Restage the gemdb command whenever it differs from what we ship - #15
Conversation
Each GciTsLogin wrote a line to the process's real stdout --
gcits login: session 0xb4b518000 lgc 0xb4b518008 rpc gem processId 84970
-- with a matching one at logout, from inside the C library. In the
extension host that is only noise in a log, but the GemDB Shell's stdout
IS the user's terminal, so it landed between the banner and the first
prompt; `gemdb -c` output that a script pipes somewhere is the same
hazard.
GciTsLogin's loginFlags argument was passing 0. It now passes
GCI_LOGIN_QUIET (0x10, from the engine's include/gci.ht). The constant is
declared in session.ts rather than gci/gciConstants.ts because that
directory is vendored from Jasper byte-for-byte and carries no login
flags at all.
One call site, and the flag turns out to cover the session's whole
lifecycle: the logout line goes too.
repl.test.ts asserts the shell's pty transcript contains no 'gcits',
which is the one place the noise is visible to a user rather than to a
log. Confirmed it fails without the flag, printing the two lines above.
The whole integration run went from dozens of these lines to zero.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reported against an extension development host: the GemDB Shell still printed `gcits login: …` after the login-quiet fix. The fix was in the extension's bundle; the terminal runs the STAGED copy at <rootPath>/bin/gemdb-shell.js, which was three days older. writeCliScripts had exactly two callers: stageGrail, which runs only when the Grail payload stamp differs, and a backstop in ensureRunning guarded by `if (!fs.existsSync(cliPath()))` -- i.e. only when the wrapper is missing outright. So an update carrying only code never restaged anything. Releases usually got away with it because bundle:grail clones Grail HEAD and the payload nearly always moves too; a developer rebuilding in the EDH never does. The guard is gone and ensureRunning now calls writeCliScripts every time. To make that cheap, writeCliScripts builds the wrapper and the driver first, fingerprints them together with out/gemdb-shell.js, and returns without touching the disk when the fingerprint matches what <rootPath>/bin/.gemdb-cli-stamp records -- so the common case does no work, and in particular no rm/copy of koffi. Content, not a version number: the bundle changes on every npm run bundle, and the wrapper bakes in the editor's own process.execPath, which moves when VS Code updates. A version stamp would miss both. The stamp is written last, so it never claims a generation that threw partway. Four tests on the new machinery: restage on a changed bundle, no writes when the bytes would be identical, restage when the wrapper differs, and no stamp left behind after a failure. Note what they do NOT cover -- the old writeCliScripts always wrote, so the bug lived entirely in its callers, and ensureRunning is not reachable from the unit suite. The call-site change is verified by inspection. 118 unit tests, 46 integration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The previous commit put the restage in ensureRunning, and the reporter
then hit "The terminal process failed to launch: Path to shell executable
/Users/…/GemDB/bin/gemdb does not exist."
requireRunning in repl.ts is why:
if (findStone() && findNetldi()) return true;
return ensureRunning(extensionPath);
With the database already up -- the common case, and the case a developer
is always in -- opening a shell returns at the first line and never
reaches ensureRunning. So the restage was in the one place the shell path
skips, and the previous commit would not have fixed the stale bundle it
was written for. It also means my suggested workaround, deleting
bin/gemdb to trip the old backstop, left the reporter with no wrapper at
all.
openRepl and runFile now call ensureCliCurrent before creating a
terminal, which is where the guarantee belongs: whoever hands a path to
VS Code as a terminal's shell program owns whether that path exists and
is current. It is cheap because writeCliScripts fingerprints first. When
generation is impossible it returns false and the command says so,
instead of VS Code reporting a launch failure that names no cause.
Verified on the real install rather than in a fixture: regenerated
~/GemDB/bin from this checkout and drove the staged shell through a pty
-- banner, prompt, and no gcits line.
One correction to the previous commit's evidence: I compared bundles with
grep for GCI_LOGIN_QUIET. out/gemdb-shell.js is minified, so that
identifier is not in either file and the counts meant nothing. The
diagnosis stands on the mtimes and on the reporter seeing the line, and
now on the pty run.
119 unit tests, 46 integration.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The first commit here was in the wrong place. Pushed a second one that fixes it. Testing the workaround I suggested — delete async function requireRunning(extensionPath: string): Promise<boolean> {
if (findStone() && findNetldi()) return true; // ← returns here
return ensureRunning(extensionPath);
}With the database already up — the common case, and permanently so for a developer — opening a shell never reaches
Verified on the real install, not a fixture: regenerated One correction to the first commit's evidence. I compared the two bundles with 119 unit tests, 46 integration. |
The GemDB Shell still printed
gcits login: …after #14 silenced it. #14 is correct — the fix was in the extension's bundle, but the terminal runs the staged copy:Why it was never refreshed
writeCliScriptshad exactly two callers:stageGrail— runs only when the Grail payload stamp differs.ensureRunning, guarded byif (!fs.existsSync(cliPath()))— only when the wrapper is missing outright.So an update carrying only code restaged nothing. Releases mostly got away with it because
bundle:grailclones Grail HEAD and the payload nearly always moves too — but a developer rebuilding in an extension development host never changes it, which is exactly the case that reported this. It would also strand a shipped shell fix in any release where Grail happened not to change.The change
The guard is gone;
ensureRunningcallswriteCliScriptsevery time. To make that cheap, the function now builds the wrapper and the driver first, fingerprints them together without/gemdb-shell.js, and returns without touching the disk when that matches<rootPath>/bin/.gemdb-cli-stamp. The common case does no work — and in particular norm/cpSyncof koffi.Content, not a version number, deliberately: the bundle changes on every
npm run bundle, and the wrapper bakes in the editor's ownprocess.execPath, which moves when VS Code updates. A version stamp would miss both.The stamp is written last, so it never claims a generation that threw partway.
Tests, and what they do not cover
Four new: restage on a changed bundle, no writes when the bytes are identical, restage when the wrapper differs, and no stamp left after a failure.
Being straight about the limit — these do not reproduce the original bug. The old
writeCliScriptsalways wrote; the bug lived entirely in its callers, andensureRunningis not reachable from the unit suite (no editor). The call-site change is verified by inspection, and the new tests exist to keep the unconditional call affordable and correct rather than to catch what went wrong.118 unit tests, 46 integration. Lint, format, both typechecks clean.
For the reporter
Opening a shell goes through
ensureRunning(statusView→gemdb.openRepl→requireRunning), so with this merged a window reload restages before the terminal opens. Without it,rm ~/GemDB/bin/gemdbtrips the old backstop and has the same effect.🤖 Generated with Claude Code