mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-16 18:05:31 +02:00
fix(gbrain-detect): classify gbrain >= 0.43 held-lock refusal as engine-locked
gbrain 0.43+ refuses a held PGLite lock with exit 1 and the message "GBrain's local database is already open through `gbrain serve` (MCP, PID N)" instead of the pre-0.43 exit 124 + "connect timed out" that the #2194 branch matches. The message matches no known pattern, so the classifier falls through to the defensive broken-config default — and Step 1.5 of /setup-gbrain and /sync-gbrain then tell the user to move a perfectly healthy config.json aside and re-init the engine. Reproduced live on gbrain 0.43.0.0, 0.44.0.0 and 0.46.30.0: with a serve holding the lock, gstack-gbrain-detect reports gbrain_local_status=broken-config; after stopping the serve it reports ok with the same untouched config. Match on the stable substring "already open through", mirroring the existing #2194 branch semantics: engine-locked for pglite, broken-db otherwise. Adds a fake-gbrain behavior for the 0.43+ refusal plus two cases (pglite -> engine-locked, postgres -> broken-db). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
committed by
Garry Tan
co-authored by
Claude Fable 5
parent
3599a3d4df
commit
5424ac5fe0
@@ -464,6 +464,17 @@ function freshClassify(env?: NodeJS.ProcessEnv): LocalEngineStatus {
|
|||||||
return configuredEngine(env) === "pglite" ? "engine-locked" : "broken-db";
|
return configuredEngine(env) === "pglite" ? "engine-locked" : "broken-db";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gbrain >= 0.43 refuses the same held-lock case with exit 1 and its
|
||||||
|
// own message: "GBrain's local database is already open through `gbrain
|
||||||
|
// serve` (MCP, PID N). This brain uses PGLite, ...". That string matches
|
||||||
|
// none of the branches above, so without this check it falls through to
|
||||||
|
// the defensive broken-config default — whose remediation tells the user
|
||||||
|
// to move a perfectly healthy config.json aside and re-init the engine
|
||||||
|
// (#2194 follow-up).
|
||||||
|
if (stderr.includes("already open through")) {
|
||||||
|
return configuredEngine(env) === "pglite" ? "engine-locked" : "broken-db";
|
||||||
|
}
|
||||||
|
|
||||||
// Probe killed by the timeout with no recognized error: the engine is
|
// Probe killed by the timeout with no recognized error: the engine is
|
||||||
// most likely healthy but slow (cold pooler connections measured at
|
// most likely healthy but slow (cold pooler connections measured at
|
||||||
// 6.9-10.7s in #1964). Don't tell the user their config is malformed.
|
// 6.9-10.7s in #1964). Don't tell the user their config is malformed.
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ interface FakeEnv {
|
|||||||
*/
|
*/
|
||||||
function makeEnv(opts: {
|
function makeEnv(opts: {
|
||||||
withGbrain?: boolean;
|
withGbrain?: boolean;
|
||||||
gbrainBehavior?: "ok" | "broken-db" | "broken-config" | "engine-locked" | "throws" | "slow" | "thin-refusal";
|
gbrainBehavior?: "ok" | "broken-db" | "broken-config" | "engine-locked" | "engine-locked-v43" | "throws" | "slow" | "thin-refusal";
|
||||||
withConfig?: boolean;
|
withConfig?: boolean;
|
||||||
/** #2051: config carries gbrain's remote_mcp thin-client marker. */
|
/** #2051: config carries gbrain's remote_mcp thin-client marker. */
|
||||||
thinClientConfig?: boolean;
|
thinClientConfig?: boolean;
|
||||||
@@ -116,7 +116,7 @@ function makeEnv(opts: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function makeFakeGbrainScript(
|
function makeFakeGbrainScript(
|
||||||
behavior: "ok" | "broken-db" | "broken-config" | "engine-locked" | "throws" | "slow" | "thin-refusal",
|
behavior: "ok" | "broken-db" | "broken-config" | "engine-locked" | "engine-locked-v43" | "throws" | "slow" | "thin-refusal",
|
||||||
): string {
|
): string {
|
||||||
// "slow": healthy engine on a cold pooler connection (#1964) — sleeps past
|
// "slow": healthy engine on a cold pooler connection (#1964) — sleeps past
|
||||||
// the (test-lowered) probe timeout, then would answer fine.
|
// the (test-lowered) probe timeout, then would answer fine.
|
||||||
@@ -141,6 +141,8 @@ exit 0
|
|||||||
? 'echo "Error: malformed config.json at ~/.gbrain/config.json" >&2'
|
? 'echo "Error: malformed config.json at ~/.gbrain/config.json" >&2'
|
||||||
: behavior === "engine-locked"
|
: behavior === "engine-locked"
|
||||||
? 'echo "gbrain sources: connect timed out (default 10000ms; pass --timeout=Ns to override)." >&2'
|
? 'echo "gbrain sources: connect timed out (default 10000ms; pass --timeout=Ns to override)." >&2'
|
||||||
|
: behavior === "engine-locked-v43"
|
||||||
|
? "echo \"GBrains local database is already open through gbrain serve (MCP, PID 12345). This brain uses PGLite, so a separate CLI process cannot open it at the same time. Stop gbrain serve, then retry this CLI command.\" >&2"
|
||||||
: behavior === "throws"
|
: behavior === "throws"
|
||||||
? 'echo "unexpected gbrain failure" >&2'
|
? 'echo "unexpected gbrain failure" >&2'
|
||||||
: behavior === "thin-refusal"
|
: behavior === "thin-refusal"
|
||||||
@@ -250,6 +252,19 @@ describe("lib/gbrain-local-status — status classification", () => {
|
|||||||
expect(localEngineStatus({ noCache: true })).toBe("engine-locked");
|
expect(localEngineStatus({ noCache: true })).toBe("engine-locked");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("returns 'engine-locked' when gbrain >= 0.43 refuses with 'already open through' and exit 1", () => {
|
||||||
|
env = makeEnv({ withGbrain: true, gbrainBehavior: "engine-locked-v43", withConfig: true });
|
||||||
|
restoreEnv = applyEnv(env);
|
||||||
|
expect(localEngineStatus({ noCache: true })).toBe("engine-locked");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("classifies the >= 0.43 held-lock refusal on a non-PGLite engine as broken-db", () => {
|
||||||
|
env = makeEnv({ withGbrain: true, gbrainBehavior: "engine-locked-v43", withConfig: true });
|
||||||
|
restoreEnv = applyEnv(env);
|
||||||
|
writeFileSync(env.configPath, JSON.stringify({ engine: "postgres", database_url: "postgres://fake" }));
|
||||||
|
expect(localEngineStatus({ noCache: true })).toBe("broken-db");
|
||||||
|
});
|
||||||
|
|
||||||
it("classifies a non-PGLite connect timeout as unreachable DB, not malformed config", () => {
|
it("classifies a non-PGLite connect timeout as unreachable DB, not malformed config", () => {
|
||||||
env = makeEnv({ withGbrain: true, gbrainBehavior: "engine-locked", withConfig: true });
|
env = makeEnv({ withGbrain: true, gbrainBehavior: "engine-locked", withConfig: true });
|
||||||
restoreEnv = applyEnv(env);
|
restoreEnv = applyEnv(env);
|
||||||
|
|||||||
Reference in New Issue
Block a user