Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions lib/gbrain-local-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,17 @@ function freshClassify(env?: NodeJS.ProcessEnv): LocalEngineStatus {
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
// 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.
Expand Down
19 changes: 17 additions & 2 deletions test/gbrain-local-status.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ interface FakeEnv {
*/
function makeEnv(opts: {
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;
/** #2051: config carries gbrain's remote_mcp thin-client marker. */
thinClientConfig?: boolean;
Expand Down Expand Up @@ -116,7 +116,7 @@ function makeEnv(opts: {
}

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 {
// "slow": healthy engine on a cold pooler connection (#1964) — sleeps past
// the (test-lowered) probe timeout, then would answer fine.
Expand All @@ -141,6 +141,8 @@ exit 0
? 'echo "Error: malformed config.json at ~/.gbrain/config.json" >&2'
: behavior === "engine-locked"
? '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"
? 'echo "unexpected gbrain failure" >&2'
: behavior === "thin-refusal"
Expand Down Expand Up @@ -249,6 +251,19 @@ describe("lib/gbrain-local-status — status classification", () => {
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", () => {
env = makeEnv({ withGbrain: true, gbrainBehavior: "engine-locked", withConfig: true });
restoreEnv = applyEnv(env);
Expand Down