* PPID watchdog mirroring the one in `MCPServer.start` — kills the proxy if * the MCP host (or its proxy of a host, see HOST_PPID_ENV) goes away without * closing stdin. Issue #277 documents why we can't rely on stdin EOF on * Linux: the parent may be SIGKILL'd and reparenting doesn't close pipes
(socket: net.Socket)
| 546 | * watchers to clean up, so this is cheap. |
| 547 | */ |
| 548 | function startPpidWatchdog(socket: net.Socket): void { |
| 549 | const pollMs = parsePollMs(process.env.CODEGRAPH_PPID_POLL_MS); |
| 550 | if (pollMs <= 0) return; |
| 551 | // Baseline from the CLI entry's earliest capture, not process.ppid here — |
| 552 | // a launcher killed during our first ~100ms would otherwise leave the |
| 553 | // baseline at 1 and blind the divergence check forever (#1185). |
| 554 | const originalPpid = EARLY_PPID; |
| 555 | const hostPpid = parseHostPpid(process.env[HOST_PPID_ENV]); |
| 556 | const timer = setInterval(() => { |
| 557 | const reason = supervisionLostReason({ |
| 558 | originalPpid, |
| 559 | currentPpid: process.ppid, |
| 560 | hostPpid, |
| 561 | isAlive: isProcessAliveLocal, |
| 562 | }); |
| 563 | if (reason) { |
| 564 | process.stderr.write(`[CodeGraph MCP] Parent process exited (${reason}); shutting down.\n`); |
| 565 | try { socket.destroy(); } catch { /* ignore */ } |
| 566 | process.exit(0); |
| 567 | } |
| 568 | }, pollMs); |
| 569 | timer.unref?.(); |
| 570 | } |
| 571 | |
| 572 | function parsePollMs(raw: string | undefined): number { |
| 573 | if (raw === undefined || raw === '') return DEFAULT_PPID_POLL_MS; |
no test coverage detected