fix(mcp): prevent "Transport closed" from a stray daemon-socket error (#974) (#983)

The client-facing MCP proxy could exit with "Transport closed" when its
connection to the shared daemon hit a socket 'error' with no listener
attached — common on WSL2 /mnt (DrvFs), where AF_UNIX is flaky. The global
fatal handler turned that uncaughtException into process.exit(1), which the
MCP client saw as a bare transport close even though the index was healthy.

proxy.ts now keeps an 'error' listener on the daemon socket for its whole
life (and skips a socket destroyed in the connect window), so a stray error
degrades to the existing in-process fallback instead of crashing. daemon.ts
releases the lockfile it acquired when it fails to bind, so the next launch
doesn't spin on a stale lock (the duplicate serve --mcp pileup).

No default behavior change for anyone; WSL /mnt users who still hit trouble
can set CODEGRAPH_NO_DAEMON=1 to skip the shared daemon entirely. Validated
on macOS (unit + live serve probe) and Linux (Docker, --init): 64/64 across
the daemon/socket/lifecycle suites, incl. real AF_UNIX.

Closes #974

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-06-24 14:48:16 -05:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 1e48861cfb
commit 7c6417ef8f
6 changed files with 164 additions and 1 deletions
+13
View File
@@ -183,6 +183,19 @@ export class Daemon {
this.server = server;
resolve();
});
}).catch((err) => {
// Bind failed — e.g. AF_UNIX is unsupported/unreliable on this filesystem
// (the WSL2 DrvFs hazard behind #974), or a stale socket we couldn't clear.
// We already hold the lockfile that `tryAcquireDaemonLock` wrote; release it
// and any partial socket so the NEXT launcher doesn't spin respawning us on
// a stale lock that points at our now-dying pid. Then re-throw so the caller
// (the bin's try/catch) exits this detached daemon cleanly and every
// launcher falls back to direct mode.
this.cleanupLockfile();
if (process.platform !== 'win32') {
try { fs.unlinkSync(this.socketPath); } catch { /* may not exist */ }
}
throw err;
});
const lock: DaemonLockInfo = {