From cd31eaf57295f985a3b2c70eff775069aeb56880 Mon Sep 17 00:00:00 2001 From: michelhelsdingen <50707906+michelhelsdingen@users.noreply.github.com> Date: Mon, 16 Feb 2026 06:31:23 +0100 Subject: [PATCH] feat: parent heartbeat for MCP server orphan prevention (#992) * feat: add parent heartbeat to MCP server to prevent orphaned processes MCP server now monitors its parent process every 30s. When the parent dies (ppid changes to 1 on Unix), the server self-exits to prevent orphaned node processes that accumulate over time. - Checks ppid every 30s after server start - Compares against initial ppid (handles reparenting) - Timer uses unref() to not keep process alive artificially - Unix-only (ppid=1 detection doesn't apply on Windows) Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude Co-Authored-By: Happy * fix: make cleanup() synchronous for consistent shutdown behavior cleanup() only does synchronous work (clearInterval + process.exit), so remove async to avoid inconsistent behavior when called from setInterval callback vs signal handler vs awaited context. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude Co-Authored-By: Happy --------- Co-authored-by: Claude Co-authored-by: Happy --- src/servers/mcp-server.ts | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/servers/mcp-server.ts b/src/servers/mcp-server.ts index 0ffeec1a..624c148f 100644 --- a/src/servers/mcp-server.ts +++ b/src/servers/mcp-server.ts @@ -307,8 +307,34 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } }); -// Cleanup function -async function cleanup() { +// Parent heartbeat: self-exit when parent dies (ppid=1 on Unix means orphaned) +// Prevents orphaned MCP server processes when Claude Code exits unexpectedly +const HEARTBEAT_INTERVAL_MS = 30_000; +let heartbeatTimer: ReturnType | null = null; + +function startParentHeartbeat() { + // ppid-based orphan detection only works on Unix + if (process.platform === 'win32') return; + + const initialPpid = process.ppid; + heartbeatTimer = setInterval(() => { + if (process.ppid === 1 || process.ppid !== initialPpid) { + logger.info('SYSTEM', 'Parent process died, self-exiting to prevent orphan', { + initialPpid, + currentPpid: process.ppid + }); + cleanup(); + } + }, HEARTBEAT_INTERVAL_MS); + + // Don't let the heartbeat timer keep the process alive + if (heartbeatTimer.unref) heartbeatTimer.unref(); +} + +// Cleanup function — synchronous to ensure consistent behavior whether called +// from signal handlers, heartbeat interval, or awaited in async context +function cleanup() { + if (heartbeatTimer) clearInterval(heartbeatTimer); logger.info('SYSTEM', 'MCP server shutting down'); process.exit(0); } @@ -324,6 +350,9 @@ async function main() { await server.connect(transport); logger.info('SYSTEM', 'Claude-mem search server started'); + // Start parent heartbeat to detect orphaned MCP servers + startParentHeartbeat(); + // Check Worker availability in background setTimeout(async () => { const workerAvailable = await verifyWorkerConnection();