gbrain >= 0.50: JSON `pglite_busy` on stdout makes `gbrain-refresh`/`setup` classify a healthy locked PGLite brain as `broken-config`
Summary
With gbrain >= 0.50, gstack-config gbrain-refresh (and ./setup) run inside a live Claude Code session classify a perfectly healthy local PGLite brain as broken-config and suppress the brain-aware blocks in every planning-skill SKILL.md.
Root cause: the detection probe gbrain sources list --json now reports the held-lock case as a JSON object on stdout with empty stderr. lib/gbrain-local-status.ts only inspects stderr, matches none of its patterns, and falls through to the defensive broken-config default. The accurate status is engine-locked, which gstack already treats as usable.
Environment
- gstack v1.87.0.0 (global git install, macOS, Darwin 27.0.0)
- gbrain 0.50.0.0,
engine: pglite, MCP registered asgbrain serve(local-stdio) - The Claude Code session's
gbrain serveMCP process holds the PGLite lock, so any CLI probe from inside that session hits the busy path.
Repro
- Have a working PGLite gbrain (
gbrain doctorok) and a Claude Code session open with the gbrain MCP registered (sogbrain serveholds the lock). - From that session run
~/.claude/skills/gstack/bin/gstack-config gbrain-refresh.
Output:
gbrain not detected (local-status: broken-config) → brain-aware blocks will be suppressed in planning-skill SKILL.md files.
Install gbrain (see /setup-gbrain) and re-run 'gstack-config gbrain-refresh' once it's configured.
gstack-gbrain-detect at the same time reports gbrain_config_exists: true, gbrain_doctor_ok: true, gbrain_engine: "pglite", but gbrain_local_status: "broken-config".
The exact probe the classifier runs:
$ gbrain sources list --json; echo "EXIT=$?"
{"error":"pglite_busy","retryable":true,"reason":"live_serve","next_action":"Wait for the current command or server to close, then retry. Do not remove a live lock."}
EXIT=1
stderr is empty. Without --json, gbrain prints the human message on stderr ("GBrain's local database is already open through gbrain serve (MCP, PID N) ..."), which the existing already open through branch (#2194 follow-up) handles correctly. The JSON mode bypasses that branch.
Impact
- Every
./setuprun inside a Claude session (including the auto-upgrade flow) silently disables gbrain integration in the planning skills, with a remediation message that tells the user to reinstall a working brain. - The 60s detection cache and
~/.gstack/gbrain-detection.jsonthen persist the wrong status.
Fix that works locally
Capture stdout from the failed execFileSync and treat the pglite_busy JSON like the held-lock stderr message:
const stderr = (e.stderr ? e.stderr.toString() : "") || "";
// gbrain >= 0.50 with --json reports the held PGLite lock as a JSON object on
// STDOUT ({"error":"pglite_busy","reason":"live_serve",...}) with empty stderr.
const stdout = (e.stdout ? e.stdout.toString() : "") || "";
...
if (stderr.includes("already open through")) {
return configuredEngine(env) === "pglite" ? "engine-locked" : "broken-db";
}
if (stdout.includes("\"pglite_busy\"") || stderr.includes("\"pglite_busy\"")) {
return configuredEngine(env) === "pglite" ? "engine-locked" : "broken-db";
}
With that patch gstack-gbrain-detect reports engine-locked and gbrain-refresh renders the brain-aware blocks:
Detected gbrain vgbrain0.50.0.0 (local-status: engine-locked).
Rendered brain-aware blocks into ~/.gstack/render/claude — now live across all your projects' Claude sessions.
A more robust variant would parse stdout as JSON when it starts with { and switch on error/reason, since gbrain's --json error envelope seems to be the direction its CLI is heading. Happy to open a PR if that shape is preferred.
Related: #2194 (same held-lock case, pre-JSON stderr message).
Source: garrytan/gstack