You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Shared-daemon sessions lose CodeGraph with "writer lock held": stop() waits on an unregistered connection, and a blocked fallback refuses instead of reading (1.6.0) #1963
Version: 1.6.0 on Linux (WSL2), several concurrent MCP sessions on one checkout.
Sessions intermittently get only CodeGraph writer lock held by PID <n> (daemon mode) back from every tool call and never recover. Two things combine:
1. Daemon.stop() can hang on a connection it accepted but never registered (src/mcp/daemon.ts:345, :381). A launcher that connects just as the daemon starts stopping is accepted, but it only joins clients after its client-hello. stop() closes the sessions in clients, then awaits server.close(), which waits on that socket. Meanwhile the daemon keeps writer.pid, and its socket is already unusable. Each new launcher fails its connect, its replacement daemon exits because the lock is taken, and after ~6 s (DAEMON_CONNECT_MAX_RETRIES × 25 ms) it falls back.
2. That fallback refuses whenever a live process holds the writer lock (makeFallbackEngine, src/mcp/index.ts:96). The session gets the error on every call. The same refusal also fires when the daemon runs a different version, and when another session's fallback already holds the lock. In that last case one fallback locks out every later session on the project for its whole lifetime.
Reproduction:
Client A attaches, and the daemon starts.
Open a raw connection to the daemon socket and send nothing, then kill -TERM the daemon. It stays alive, writer.pid stays, the socket is gone.
Client B's first tool call fails after 6.07 s with "writer lock held (daemon mode)".
Close the raw connection. The daemon exits, and B's next call succeeds in-process, with B now holding the lock in fallback mode.
Client C fails the same way, with "held by PID (fallback mode)".
Fixes we're running in a fork, with tests:
stop() tracks every accepted socket, refuses connections that arrive while stopping, and destroys them all before server.close().
When a live process holds the writer lock, makeFallbackEngine returns new MCPEngine({ watch: false }): it serves reads, starts no watcher, and claims no lock. The unreadable-lock and legacy-daemon refusals stay, as does direct mode's refusal.
Happy to open PRs based on main for either or both.
Opened #1979 from main with both changes as you described them, plus a regression test for the unregistered socket on stop. If you'd rather land your own branch, say so and I'll close mine.
Thanks for the detailed report, @bompus, and thanks for using CodeGraph! This is fixed on main by #2042, which builds on @danusha2345's #1979 (thank you!): a shared daemon now closes connections still mid-handshake when it stops, and an in-process fallback that finds another process holding the writer lock serves reads from the existing index (without auto-sync) instead of refusing, so sessions no longer lose CodeGraph with "writer lock held". The fix will be in the next release.
Activity
danusha2345 commentedon Sep 26, 2026
Opened #1979 from
mainwith both changes as you described them, plus a regression test for the unregistered socket on stop. If you'd rather land your own branch, say so and I'll close mine.Merge codex/integration-all-2026-09-26 (colbymchenry#1959, colbymchen…
fix(mcp): log when a fallback serves reads without auto-sync (colbymc…
fix(mcp): log when a fallback serves reads without auto-sync (colbymc…
Merge codex/integration-all-2026-09-26 (colbymchenry#1987, colbymchen…
fix(mcp): stop pending connections and serve read-only fallbacks (#1963…
colbymchenry commentedon Sep 27, 2026
Thanks for the detailed report, @bompus, and thanks for using CodeGraph! This is fixed on
mainby #2042, which builds on @danusha2345's #1979 (thank you!): a shared daemon now closes connections still mid-handshake when it stops, and an in-process fallback that finds another process holding the writer lock serves reads from the existing index (without auto-sync) instead of refusing, so sessions no longer lose CodeGraph with "writer lock held". The fix will be in the next release.Merge upstream/main (71 commits, through e63fe2e) into fork/consolidated
fix(windows): make load-sensitive tests wait on real signals; longer …
merge: sync upstream v1.6.1 (f4ddf50, 2026-10-01)
docs(fork): record the v1.6.1 sync; name the user, not a first name