[BUG] Failed stdio MCP server retries leak orphaned processes indefinitely during a long-running session (macOS)
Description
When a stdio MCP server's connection attempt fails (e.g. bad DB credentials for a postgres-mcp-style server), the underlying subprocess doesn't exit — it retries its own connection pool internally forever. Claude Code's client-side 30s connect timeout gives up waiting and reports CONNECT_TIMEOUT, but never kills the already-spawned subprocess. Some time later (observed: ~every 15 minutes) a periodic reconnect attempt spawns a new subprocess for the same server, while the earlier stuck one is abandoned and reparented to launchd (PPID 1) — it is never reaped.
Over a ~3 day period with 5 long-running sessions each configured with a failing postgres-dev MCP server, this produced 373 orphaned processes (186 failed connection attempts × ~2 processes each: the uv tool uvx wrapper + its Python child).
This is a different trigger than #93087 (which is scoped to session-end cleanup) and a different platform/mechanism than #90362 (Windows, node.exe, unclear root cause). Here the root cause is fully isolated: the subprocess survives the client's timeout because nothing sends it a kill signal, and periodic reconnection compounds this by never checking for/cleaning up the previous attempt.
Environment
- Platform: macOS (Darwin 25.6.0)
- Claude Code sessions: 5 concurrent, some running via
--continue --settings ... --dangerously-load-development-channels, uptime 2–3 days - Affected MCP server: a
postgres-mcpstdio server (viauvx --python 3.12 --with "mcp>=1.5,<2" postgres-mcp) configured withDATABASE_URIpointing at a Postgres instance with incorrect password
Root cause (isolated by manually reproducing)
Running the exact same command Claude Code uses:
DATABASE_URI="postgresql://postgres:<wrong-password>@<host>:5432/postgres" \
uvx --python 3.12 --with "mcp>=1.5,<2" postgres-mcp
produces:
WARNING error connecting in 'pool-1': connection failed: ... FATAL: password authentication failed for user "postgres"
WARNING error connecting in 'pool-1': connection failed: ... FATAL: password authentication failed for user "postgres"
... (repeats with growing backoff, process never exits) ...
The process just keeps retrying forever and never sends an initialize response, so from Claude Code's side it looks identical to a slow/hung server, not a fast failure. After the 30s timeout, Claude Code moves on but leaves the subprocess running.
Reproduction Steps
- Configure a stdio MCP server whose backend will reject connections immediately but whose client library retries the connection pool internally without exiting (several DB-backed MCP servers do this — asyncpg-based pools especially).
- Start a long-running Claude Code session (
--continue, or any session left open) with this server configured. - Leave the session running for a few hours.
- Observe:
ps -ax -o pid,ppid,etime,command | grep <server-binary>shows a new orphaned (PPID 1) process appearing periodically (~every 15 min in our case), while old ones are never cleaned up.
Evidence
$ ps -ax -o pid,ppid,etime,command | grep postgres-mcp | grep -v grep | wc -l
186 # distinct failed connection attempts, all orphaned, spanning ~3 days
Timestamps of orphaned process start times cluster almost exactly on 15-minute boundaries (:14:34, :29:34, :44:34, :59:34), consistent with a periodic reconnect trigger.
Expected Behavior
- When the client-side connect timeout (30s) elapses without a successful
initialize, Claude Code should kill the underlying subprocess (SIGINT → SIGTERM → SIGKILL escalation, same as used for normal shutdown) rather than abandoning it. - Before spawning a new reconnect attempt for a server, Claude Code should verify the previous attempt's subprocess has actually exited, or explicitly kill it first.
Impact
- Unbounded process accumulation for any long-running session with a misconfigured/unreachable DB-backed (or any internally-retrying) MCP server — in our case ~570MB RAM and severe swap pressure after a few days, requiring manual
killcleanup of hundreds of processes. - The symptom (
CONNECT_TIMEOUTon every session start) gives no indication to the user that this is silently leaking processes in the background.
Related Issues
- #93087 — orphaned stdio servers at session end (different trigger: our sessions never ended)
- #90362 — similar symptom on Windows, root cause not isolated there
Workaround
We wrote an external launchd job that periodically kills any MCP-server-pattern process with PPID 1 older than 20 minutes. This is a workaround, not a fix — it doesn't address the underlying subprocess leak.