Fix first-run setup failing with ENOENT on the Grail stamp - #11
Merged
jgfoster merged 1 commit intoAug 25, 2026
Merged
Conversation
A user's first install died just after "Database created":
Setting up GemDB failed: ENOENT: no such file or directory,
open '/Users/…/GemDB/grail/.gemdb-grail-stamp'
prepareFiles recorded Grail as filed into the database before anything
had created <rootPath>/grail for the record to live in. Python support
was then never staged, and every retry failed the same way.
The same misordering broke upgrades, more quietly. Where the directory
did exist, the stamp was written into the PREVIOUS version's copy;
grailNeedsUpdate then compared the bundled stamp with the bundled stamp,
found them equal, and skipped staging -- so a new version's payload never
reached disk, wearing the new version's label, and writeCliScripts never
re-ran to refresh bin/gemdb. It could not have worked either way, since
stageGrail replaces the directory wholesale and deletes the stamp it was
just handed.
Neither failure reproduces on a machine that has run an earlier version,
which is how both reached a release: every developer machine already had
the directory, and the integration suite calls stageGrail itself rather
than going through prepare.
The order now lives in one named place, stageAndRecordGrail, with the
reasons attached. The condition moved too: createDatabase returns
{created, preloaded} so the stamp follows this call having made the
database from the shipped extent, rather than the extension merely
shipping one. An upgrade finds a database carrying whatever Grail was
filed into it before, and stamping there would claim an install that
never happened; leaving it unstamped is what sends ensureRunning to file
the new Grail in.
src/__tests__/grailStaging.test.ts starts from a root path that does not
exist, which is the condition the existing tests could not express. Two
of its five cases fail with the calls reversed -- one reproducing the
reported ENOENT verbatim -- and database.test.ts pins the new return.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jgfoster
force-pushed
the
fix/first-run-grail-staging
branch
from
August 24, 2026 23:27
fc33a93 to
add7887
Compare
jgfoster
changed the base branch from
main
to
fix/grail-retired-canonical-flag
August 24, 2026 23:27
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A user's first install died immediately after creating the database:
prepareFilesrecorded Grail as filed into the database before anything created<rootPath>/grailfor that record to live in. Python support was then never staged, and every retry failed identically — a permanent first-run failure.Note what is missing from their log: no
Staging Grail …line betweenDatabase createdand the error. That is the tell.The same misordering broke upgrades, more quietly
Where
<rootPath>/graildid exist, the stamp was written into the previous version's directory.grailNeedsUpdatethen compared the bundled stamp against the bundled stamp, found them equal, and skipped staging. So:writeCliScriptsnever re-ran, leavingbin/gemdbat its old contents.It could not have worked in either order, in fact:
stageGrailreplaces the directory wholesale, so it deletes the stamp it was just handed.Why it reached a release
Neither failure reproduces on a machine that has already run an earlier version — the directory is simply there. And the integration suite calls
stageGrailitself against a fixture, so it never goes throughprepareat all. The gap was a first-run path with no test that could reach it.The fix
The order now lives in one named place with the reasons attached:
The condition moved too, and it matters as much as the order.
createDatabasenow returns{created, preloaded}, so the stamp follows this call having made the database from the shipped extent rather than "the extension ships an extent". An upgrade finds a database carrying whatever Grail was filed into it before; stamping there would claim an install that never happened. Leaving it unstamped is exactly what sendsensureRunningto file the new Grail in.Tests
src/__tests__/grailStaging.test.tsstarts from a root path that does not exist — the condition the existing tests could not express. Reverting to the old order fails two of its five cases, one reproducing the reported error verbatim:database.test.tspins the new return value, including that a second call creates nothing.114 unit tests (was 109), 46 integration. Lint, format, both typechecks clean.
For the affected user
Deleting
~/GemDB/db(or the whole~/GemDB) and reloading gives the fast path: the database is recreated from the shipped extent and stamped, no file-in. Without that, the fix still recovers them — Grail stages, and because the database already exists it is correctly left unstamped, so the first Python they run files it in, which takes a few minutes once.🤖 Generated with Claude Code