fix(gbrain): a slow --version probe classifies as timeout, never no-cli (#2716)

resolveGbrainBin's bare catch collapsed 'gbrain missing' and 'gbrain present
but the 2s --version budget expired' into the same null — freshClassify then
said no-cli, which the --is-ok whitelist from #1964 does NOT forgive, so a
bun-shim install on a loaded POSIX box silently lost every brain-aware block.
The probe now returns a discriminated result (cached per-process, same
lifetime the old null had) using the same killed/SIGTERM/ETIMEDOUT
discrimination the sources-list probe below already uses; timeout routes to
the forgiven 'timeout' status. GSTACK_GBRAIN_VERSION_PROBE_TIMEOUT_MS test
override added (same precedent as the sources-probe override).

Receipt: the slow-but-present sibling test fails on a v1.77.0.0 scratch
worktree (classifies no-cli there).

Fixes #2716

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-31 21:15:49 +00:00
co-authored by Claude Fable 5
parent 84c0eee9f2
commit 262a605794
2 changed files with 67 additions and 13 deletions
+27 -2
View File
@@ -62,7 +62,7 @@ interface FakeEnv {
*/
function makeEnv(opts: {
withGbrain?: boolean;
gbrainBehavior?: "ok" | "broken-db" | "broken-config" | "engine-locked" | "engine-locked-v43" | "throws" | "slow" | "thin-refusal";
gbrainBehavior?: "ok" | "broken-db" | "broken-config" | "engine-locked" | "engine-locked-v43" | "throws" | "slow" | "slow-version" | "thin-refusal";
withConfig?: boolean;
/** #2051: config carries gbrain's remote_mcp thin-client marker. */
thinClientConfig?: boolean;
@@ -116,8 +116,18 @@ function makeEnv(opts: {
}
function makeFakeGbrainScript(
behavior: "ok" | "broken-db" | "broken-config" | "engine-locked" | "engine-locked-v43" | "throws" | "slow" | "thin-refusal",
behavior: "ok" | "broken-db" | "broken-config" | "engine-locked" | "engine-locked-v43" | "throws" | "slow" | "slow-version" | "thin-refusal",
): string {
// "slow-version": gbrain IS installed but even `--version` blows the
// (test-lowered) budget — the #2716 bun-shim-on-a-loaded-POSIX-box shape.
// Must classify as "timeout" (usable, --is-ok forgives), never "no-cli".
if (behavior === "slow-version") {
return `#!/bin/sh
sleep 2
echo "gbrain 0.43.0.0"
exit 0
`;
}
// "slow": healthy engine on a cold pooler connection (#1964) — sleeps past
// the (test-lowered) probe timeout, then would answer fine.
if (behavior === "slow") {
@@ -222,6 +232,21 @@ describe("lib/gbrain-local-status — status classification", () => {
expect(localEngineStatus({ noCache: true })).toBe("no-cli");
});
// #2716: a present-but-slow gbrain (bun-shim install on a loaded POSIX box)
// used to collapse into the same `null` as a missing binary — classified
// "no-cli", which `--is-ok` does NOT forgive, so every brain-aware block
// silently disappeared. Slow-but-present must classify "timeout" (forgiven).
it("returns 'timeout' (not 'no-cli') when the --version probe blows its budget", () => {
env = makeEnv({ withGbrain: true, gbrainBehavior: "slow-version", withConfig: true });
restoreEnv = applyEnv(env);
process.env.GSTACK_GBRAIN_VERSION_PROBE_TIMEOUT_MS = "300";
try {
expect(localEngineStatus({ noCache: true })).toBe("timeout");
} finally {
delete process.env.GSTACK_GBRAIN_VERSION_PROBE_TIMEOUT_MS;
}
});
it("returns 'missing-config' when CLI is present but ~/.gbrain/config.json absent", () => {
env = makeEnv({ withGbrain: true, gbrainBehavior: "ok", withConfig: false });
restoreEnv = applyEnv(env);