Skip to content

fix(mcp): daemon stop closes pending sockets; a blocked fallback serves reads (#1963) - #1979

Closed
danusha2345 wants to merge 2 commits into
colbymchenry:mainfrom
danusha2345:fix/1963-daemon-stop-fallback
Closed

danusha2345 wants to merge 2 commits into
colbymchenry:mainfrom
danusha2345:fix/1963-daemon-stop-fallback

Conversation

@danusha2345

Copy link
Copy Markdown
Contributor

Fixes #1963. This implements the two changes @bompus proposed in the issue.

Problem

  • Daemon.stop() can hang. 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 unregistered socket. The daemon keeps writer.pid while its socket is already unusable.
  • The fallback refuses. When a live process holds the writer lock, makeFallbackEngine throws "writer lock held". The same happens on a daemon version mismatch, and when another session's fallback holds the lock, so one fallback locks every later session out of CodeGraph.

Fix

  • Daemon tracks every accepted socket, destroys new connections that arrive while it is stopping, and destroys all tracked sockets before server.close(). A hello that resolves after stop began no longer creates a session.
  • When a live daemon or another process holds the writer lock, the fallback returns new MCPEngine({ watch: false }). It serves reads from the WAL database, starts no watcher and claims no lock. The unreadable-lock and legacy-daemon refusals are unchanged.

Tests

mcp-daemon.test.ts:

  • a daemon with a raw connection that never sends its hello exits on SIGTERM within 1.5 s;
  • the two existing refusal tests (writer lock held, version mismatch) now expect a read-only codegraph_status answer, and still check that no writer.pid was claimed.

Full suite on this branch (Linux, Node 22, native kernel): all tests pass except the known extraction.test.ts pool-worker crash from #1779 (fixed by #1883), which is intermittent and also occurs on main; that file alone passes 655/655.

🤖 Generated with Claude Code

@bompus

bompus commented Sep 27, 2026

Copy link
Copy Markdown
Contributor

Thanks, go ahead with yours. I compared it with what our fork runs and the behavior matches: stop destroys pending sockets and drops connections that arrive mid-stop, and the fallback serves reads with no lock and no watcher, while the unreadable-lock and legacy-daemon refusals stay. I won't open a competing PR.

One optional extra: our fork writes a line to stderr when it falls back to read-only, e.g. [CodeGraph MCP] Serving reads in-process without auto-sync: <holder>. Otherwise a session that has quietly stopped syncing looks the same as a healthy one in the logs.

…henry#1963)

A read-only fallback starts no watcher and holds no writer lock, so a
session that quietly stopped syncing looked the same as a healthy one in
the logs. Name the holder on stderr when the fallback goes read-only.
Suggested by @bompus in colbymchenry#1979.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@danusha2345

Copy link
Copy Markdown
Contributor Author

Thanks for checking it against your fork. Added the stderr line in 364d379: a read-only fallback now logs [CodeGraph MCP] Serving reads in-process without auto-sync: writer lock held by PID <n> (<mode> mode)., or live daemon PID <n> holds the project lock. when a daemon it cannot attach to holds the lock. Both fallback tests now assert the line.

danusha2345 pushed a commit to danusha2345/codegraph that referenced this pull request Sep 27, 2026
…henry#1963)

A read-only fallback starts no watcher and holds no writer lock, so a
session that quietly stopped syncing looked the same as a healthy one in
the logs. Name the holder on stderr when the fallback goes read-only.
Suggested by @bompus in colbymchenry#1979.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
colbymchenry added a commit that referenced this pull request Sep 27, 2026
…) (#2042)

* fix(mcp): close pending sockets and allow read-only fallback (#1963)

* fix(mcp): log when a fallback serves reads without auto-sync (#1963)

A read-only fallback starts no watcher and holds no writer lock, so a
session that quietly stopped syncing looked the same as a healthy one in
the logs. Name the holder on stderr when the fallback goes read-only.
Suggested by @bompus in #1979.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(mcp): stop pending connections and serve read-only fallbacks (#1963)

Pending handshakes could block daemon shutdown or register phantom sessions after disconnect.
Track accepted sockets and reject late hello continuations.
Use explicit read-only engines and SQLite connections for blocked fallbacks, suppressing synchronization, watching, migrations, and repairs.
Preserve writer ownership and the existing project lifecycle, with regression coverage for #1963 and #1356.

Co-authored-by: danusha2345 <danusha2345@users.noreply.github.com>
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: danusha2345 <danusha2345@users.noreply.github.com>
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@colbymchenry

Copy link
Copy Markdown
Owner

Thanks @danusha2345! Your commits here were carried into #2042 (authorship preserved), with a few follow-up changes from review, and that is now merged. Closing this one in favour of it. It will be in the next release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants