Thanks to visit codestin.com
Credit goes to github.com

Skip to content

gbrain serve (local-stdio MCP) holds PGLite exclusively; probe exit-124 misclassified as broken-config — /sync-gbrain deterministically skips code+memory inside any Claude session (lock variant of #1964, timeout raise cannot fix) #2194

Description

@Kerumen

Symptom

Same output as the #1964 family (#1792 #1832 #1969 #2051), but with a root cause none of them cover:

gstack-gbrain-sync (incremental):
  SKIP  code         skipped — local engine broken-config — config at ~/.gbrain/config.json is malformed; see /setup-gbrain Step 1.5 (0.9s)
  SKIP  memory       skipped — local engine broken-config — config at ~/.gbrain/config.json is malformed; see /setup-gbrain Step 1.5
  OK    brain-sync   curated artifacts pushed (2.8s)

Meanwhile the engine is healthy: gbrain doctor --fast --json answers with only warnings, and ~/.gbrain/config.json is valid, well-formed JSON (pglite engine, voyage-code-3 embeddings).

Root cause: gbrain serve holds the PGLite database exclusively

PGLite is an embedded single-process engine — whichever process opens ~/.gbrain/brain.pglite owns it. With the recommended default setup (/setup-gbrain → PGLite + local-stdio MCP), Claude Code spawns gbrain serve at session start, and serve opens the database eagerly at boot — even if no mcp__gbrain__* tool is ever called in the session:

$ pgrep -fl gbrain
95427 bun /Users/.../.bun/bin/gbrain serve        # parent PID = the live `claude` process

$ lsof +D ~/.gbrain/brain.pglite | head -3
COMMAND   PID          USER   FD   TYPE   ... NAME
bun     95427 yannpringault    6u   REG   ... .../brain.pglite/pg_wal/000000010000000000000004
bun     95427 yannpringault    7u   REG   ... .../brain.pglite/global/2672

Every gbrain CLI command that needs the DB then times out at connect:

$ gbrain sources list --json
gbrain sources: connect timed out (default 10000ms; pass --timeout=Ns to override).
$ echo $?
124

freshClassify in lib/gbrain-local-status.ts probes exactly that command. The failure matches none of its patterns — stderr contains neither "Cannot connect to database" nor "config.json", and the process wasn't killed by the classifier's own timer (gbrain exits 124 on its internal timeout, so e.killed/SIGTERM/ETIMEDOUT are all false) — and it falls through to the defensive default at lib/gbrain-local-status.ts:304: broken-config. The user is told their valid config is malformed.

Killing the serve process unblocks everything instantly — gbrain sources list --json returns in well under a second and the re-run syncs clean (OK code ... page_count=130). Which confirms it was a lock, not a slow engine.

Why this is distinct from #1964 (and why its fix makes this variant worse)

#1964's cause is a slow connection (Supabase transaction-mode pooler, 6.9–10.7s) — raising the probe timeout fixes it. This variant is a held lock: the probe can never succeed while any Claude session is alive, because serve spawns per-session and owns the engine for the session's lifetime. A longer timeout just makes /sync-gbrain wait longer before mislabeling the same lock.

It also hits harder than the pooler variant: it affects the default setup path (PGLite + local-stdio is what /setup-gbrain recommends), and it's deterministic from the second session onward. It only ever works on setup day because the first sync runs before a session restart spawns serve. Every /sync-gbrain run inside a live Claude session hits it.

Suggested fixes

  1. Classifier (gstack, small): treat gbrain's own connect-timeout as its own state — match "connect timed out" in stderr and/or exit code 124 → a new engine-locked/timeout classification instead of the broken-config defensive default. Bonus: when the engine is pglite, check for a gbrain serve process (or lsof on the database path) and name the holding pid in the SKIP message — "database held by gbrain serve (pid N); stop it or run the sync outside a Claude session".
  2. Skill docs (gstack, tiny): /sync-gbrain's safety note already warns about racing gbrain autopilot (/sync-gbrain can trigger gbrain's destructive auto-recovery, wiping user repos #1734); the session's own MCP serve process is the same class of conflict and deserves the same sentence.
  3. Upstream (gbrain): the CLI should either proxy through a running serve or fail with "database held by pid N" instead of a generic connect timeout. Happy to file that on the gbrain repo if useful — flagging it here first since gstack owns the misdiagnosis and the user-facing message.

Environment

  • gstack 1.58.5.0, gbrain 0.42.57.0, macOS (darwin 24.6.0)
  • Engine: PGLite, embeddings voyage-code-3 (1024d), schema pack gbrain-base-v2
  • MCP: local-stdio, registered user-scope in Claude Code (spawns gbrain serve per session)
  • Workaround that recurs every session: kill $(pgrep -f "gbrain serve")GSTACK_DETECT_NO_CACHE=1 /sync-gbrain (the 60s classifier cache otherwise replays the stale negative)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions