Repository navigation
Description
Environment
- Platform (select one):
- Anthropic API
- AWS Bedrock
- Google Vertex AI
- Other:
- Claude CLI version: 1.0.18 (Claude Code)
- Operating System: macOS Darwin 24.5.0
- Terminal: Terminal/zsh
Bug Description
Claude Code does not properly clean up MCP server processes when exiting, leading to orphaned processes that continue consuming system resources indefinitely. These
processes accumulate over multiple sessions and are not cleaned up even when MCP servers are removed from configuration.
Steps to Reproduce
- Configure one or more MCP servers using
claude mcp add <name> <command> - Start Claude Code and use it normally (any session that might initialize MCP servers)
- Exit Claude Code normally
- Check running processes with
ps aux | grep mcp - Observe orphaned MCP server processes still running
- Optional: Try
claude mcp remove <name>- processes still persist
Expected Behavior
MCP server processes should be gracefully terminated when Claude Code exits. All spawned MCP server processes should be cleaned up, leaving no orphaned processes consuming
system resources.
Actual Behavior
MCP server processes remain running as orphaned processes after Claude Code exits. These processes:
- Continue consuming system resources (CPU, memory)
- Accumulate over time with each Claude Code session
- Are not cleaned up even when running
claude mcp remove <server-name> - Persist across system restarts in some cases
Additional Context
Evidence from testing:
- Found 40+ orphaned MCP server processes from previous Claude Code sessions
- Processes dating back several days (some running since Monday/Tuesday)
- Process types include:
- Docker containers:
docker run -i --rm mcp/time,mcp/github,mcp/memory, etc. - Python processes:
chroma-mcp,cli-mcp-server - Node.js processes:
context7-mcp
- Docker containers:
Example orphaned processes (sanitized):
user 90260 0.0 0.0 411858096 12272 s001 S+ 11:19AM 0:00.56 docker run -i --rm mcp/time
user 88027 0.0 0.0 411869888 12224 ?? S 11:18AM 0:00.53 docker run -i --rm mcp/time
user 54415 0.0 0.0 411896512 4576 ?? S Tue12PM 0:02.82 docker run -i --rm mcp/time
user 90680 0.0 0.0 411871728 10912 ?? S Mon11AM 0:04.81 docker run -i --rm mcp/time
user 54413 0.0 0.0 411889136 4080 ?? S Tue12PM 0:02.74 docker run -i --rm -e GITHUB_PERSONAL_ACCESS_TOKEN=*** mcp/github
user 54418 0.0 0.0 411896816 4128 ?? S Tue12PM 0:02.85 docker run --rm -i --mount type=bind,src=/Users/user/Projects/workspace,dst=/projects/workspace mcp/git
Impact:
- Resource leak with long-running orphaned processes
- Potential system performance degradation over time
- User confusion when debugging MCP server issues
- Accumulating Docker containers consuming disk space
Potential root cause:
The MCP protocol specification includes a graceful shutdown phase, but Claude Code appears to not properly invoke cleanup handlers during application exit to terminate
spawned MCP server child processes.
Activity
ashwin-ant commentedon Jun 11, 2025
We have a fix on the way!
ashwin-ant commentedon Jun 13, 2025
This should be fixed in 1.0.21. Thanks for reporting @noomz!
cbusillo commentedon Jun 18, 2025
I am on version 1.0.27 and seem to have this same issue. I have dozens of node processes. I am on the developer beta of macOS, is that an issue?
CoreyWinkelmannPP commentedon Jun 23, 2025
I can confirm this issue still exists on Claude Code v1.0.31 with a custom MCP server.
Additional context: The same custom MCP server is properly cleaned up when used with GitHub Copilot (process terminates correctly when stopping the server), which indicates that:
My MCP server correctly handles SIGINT termination signals
The issue is specific to Claude Code's MCP process management, not a general MCP server problem
This suggests Claude Code is not sending the proper termination signals to MCP server child processes on exit, while other MCP clients (like GitHub Copilot) handle this correctly.
Environment:
Claude Code version: v1.0.31
MCP Server: Custom server (properly handles SIGINT as verified with GitHub Copilot)
Platform: macOS 15.5
The bug persists across multiple versions (original report was v1.0.18), indicating this hasn't been resolved yet.
mstllc commentedon Jun 26, 2025
Same with v1.0.35. MCP server is a docker container that is persisted until I shut it down and delete it. A new container is instantiated with each call to
claudecommand.ashwin-ant commentedon Jun 27, 2025
Thanks, we'll look into this again.
41 remaining items
limaronaldo commentedon May 26, 2026
I may have hit a downstream instance of this with
claude-mem+chroma-mcpon macOS.In my case, dozens of
chroma-mcpprocesses accumulated and pointed at the same persistent Chroma data dir (~/.claude-mem/chroma). This caused the HNSWlink_lists.binfile to balloon to ~1.67T apparent / 244G allocated, while the actual collection only had ~85K 384-dim embeddings.Environment:
I suspect Claude Code/Zed multiple sessions amplified the issue, but the direct corruption path was multiple
chroma-mcpwriters surviving at the same time.There is a more specific
claude-memissue because the actionable downstream fix appears to be in its ChromaMcpManager lifecycle/cleanup logic:thedotmack/claude-mem#2638
fix(server): exit when MCP parent process disappears (#217)
wojciecholszewski commentedon Jun 8, 2026
Fresh reproduction in the wild, mid-2026 — adding a concrete data point since this is still happening and the thread is active.
Environment
~/.claude.json.What I observed
For a single stdio MCP server (
@a-bonus/google-docs-mcp@1.4.0,node ... index.js),ps -Ao pid,ppid,pcpu,etime,command | grepshowed 59 live instances, all withPPID=1(reparented to launchd). Ages ranged from ~2 minutes to ~3 days. Each idle instance was burning ~10–19% CPU (busy-loop bug in that particular server, separate problem — see workaround note below), so together they were eating ~420% CPU and ~1.2 GB RSS continuously, on top of being functionally dead.These were not active children of any running
claudeprocess — every parent was init. The cleanup never happened on Ctrl+C / pane close / SIGHUP from the terminal closing / crash. The only thing that scales the leak is how many distinct sessions you open over time.Why this matters even if the MCP server is "well-behaved"
On any stdio MCP server that happens to busy-loop while idle (and there are several in the wild), every orphaned instance is a permanent CPU+RAM tax until the user notices and runs
pkill. A well-behaved server hides the leak but doesn't fix it —ps | wc -lstill grows unbounded across sessions.Workaround pattern other projects are adopting
a-bonus/google-docs-mcpshipped a fix in PR #112 (v1.9.0, 2026-04-26): "fix(stdio): kill orphaned servers via signal handlers + parent-PID watchdog". The MCP server polls its owngetppid(); when it sees1, it self-terminates. This works, but it pushes the lifecycle problem onto every MCP server author individually — which is exactly the pattern the MCP spec was supposed to avoid by making the host process responsible for child lifecycle.Concrete asks for Claude Code
PPID=1— many users won't notice until they're hundreds deep.Happy to share a sanitized
pssnapshot or run any diagnostic if useful.reikys commentedon Jul 2, 2026
Still reproducible on v2.1.198 (macOS, Darwin 25.5.0). Some data that may help pinpoint the fix:
After a few weeks of normal use I found 75 orphaned stdio MCP server processes, all reparented to launchd (
PPID=1):Two observations:
Claude Code exits without signaling its stdio MCP children. Every one of these servers had
SIGINT/SIGTERMhandlers installed that callprocess.exit(0)— if the client had sent SIGTERM on exit, they would have terminated. They didn't, so no signal is being delivered on (at least some) exit paths.The MCP spec's stdio shutdown sequence says the client SHOULD close the child's stdin, wait for exit, then escalate to SIGTERM and finally SIGKILL. Claude Code appears to stop after closing stdin (or skips even that on abnormal exit), so any server whose event loop is kept alive by timers/sockets lingers forever and keeps consuming resources.
Workaround for server authors until this is fixed — self-detect parent death:
Given the number of closed duplicates of this report (#40667, #42881, #60747; #66280 still open for the subagent variant), a SIGTERM-with-timeout follow-up in the client's shutdown path would resolve this whole class of leaks.
omelnickiy commentedon Jul 5, 2026
Another reproduction with a twist: this also happens without the user ever exiting Claude Code — Claude Desktop's local agent mode (CCD) recycles its embedded
claudeprocesses mid-session, abandoning the MCP children each time.Environment
uv run(Python) andbun(plugin MCP)What happens
Within a 30-minute window of normal Claude Desktop usage (one chat session plus one app restart), the desktop app launched the embedded
claudebinary 4 times for the same logical session (first query, a later query cycle, and warm-ups after an app restart —main.logshows[WarmLifecycle:preview] Warming up session ...events matching the spawn times). Each launch spawned the full set of configured stdio MCP servers; each replacedclaudeprocess died without terminating its children.Result: 4 orphaned copies of each MCP server, reparented to launchd:
All orphans had
CLAUDE_CODE_ENTRYPOINT=claude-desktopand__CFBundleIdentifier=com.anthropic.claudefordesktopin their environment, confirming the desktop app (not a terminal session) as the launcher.Two compounding factors
claudeprocesses (query cycles, warm-lifecycle, app restart) without killing the children's process group.uv runthe Python process's own PPID never changes because the intermediateuvprocess survives) accumulate indefinitely.Workaround that works
We patched the Python server with a watchdog thread: capture the grandparent PID (
uv's parent = theclaudehost) at startup, poll it withos.kill(pid, 0)every 10s, andos._exit(0)when it disappears (also exit if own PPID becomes 1, and hard-exit after the stdio server returns). Verified: orphans now self-terminate ~10s after the host dies.A proper fix on the host side would be killing the spawned MCP servers' process group (or at least the direct children and their descendants) whenever a
claudeprocess is replaced or torn down — including the desktop warm-lifecycle path, not just interactive CLI exit.machineghost commentedon Jul 17, 2026
Claude:
Another data point — orphaned
google-workspace-mcprunning for 15 days on LinuxDiscovered an orphaned MCP server process (
google-workspace-mcp) that had been running for 15 days after its parent Claude Code session ended. The process tree (npm exec→sh→node) was reparented to systemd (PPID=1), consuming ~580 MB RSS and 25% CPU with no client connected.google-workspace-mcp(stdio transport, configured in project.claude.json)Process groups (
setpgid/kill(-pgid)) or a cleanup-on-startup sweep for orphaned children would fix this class of bug generically, without users needing per-tool workarounds.Me:
Claude also helpfully pointed out that I could keep making wrappers (like the one I evidently had it make awhile ago for a dev server it runs frequently) to "solve" this. I can't express how wrong it is that every Claude user should have to write separate wrapper for all the apps Claude might run ... instead of one dev (at Anthropic), writing one fix, once, for one app (Claude)!
inspiredearth commentedon Aug 27, 2026
Adding a data point that reinforces this issue with a concrete functional failure, not just resource waste — plus direct log evidence of one specific trigger: daemon self-restart on version upgrade.
Environment
claude mcp add(user-scoped and project-scoped), mostlynpx-launched Node packages, onebun-launched, oneuvx/Python (mcp[cli]), plus anmcp-remotestdio proxyConfirmed trigger: daemon self-restart leaks the prior generation
~/.claude/daemon.logshows the pattern directly — a line like:...is followed by the supervisor spawning a fresh set of MCP server subprocesses without terminating the previous generation's children. Same thing happens across a manual
/exit+ relaunch.Real
ps auxsnapshot showing 3 live generations at onceWith zero active Claude Code sessions/agents open, I had three separate generations of nearly every locally-spawned MCP server alive simultaneously — each generation identifiable by a distinct start timestamp on an otherwise-identical command line (
12:46PM,12:47PM,12:48PM, from one daemon-triggered restart plus two manual/exit+relaunch cycles):Every
npx-launched server also leaves itsnpm exec <pkg>wrapper process running orphaned alongside the actual binary — so it's not just one leaked process per server, it's (at least) two.Beyond resource waste: this actually breaks tool availability
I built a single-instance guard into my own custom MCP server — an
flock-based lock held for the process's lifetime — specifically to stop two copies of the same server ever running concurrently, because two instances had previously raced and corrupted output.Because Claude Code's restart doesn't terminate the prior generation, the orphaned old instance kept holding the lock, so the new legitimate instance spawned by the fresh session failed to start. Every reconnect attempt failed instantly, surfaced in Claude Code as
CONNECTION_CLOSED, and the server's tools were unusable until I manually found and killed the orphaned PID viaps aux/kill.This means the orphaning bug isn't just a leak — for any MCP server author using a single-instance lock (a reasonable pattern to prevent concurrent-write corruption), it's a reliability regression that breaks the new session, not just a cost to the old one.
Root cause (matches the analysis in #33947)
prctl(PR_SET_PDEATHSIG)equivalent, so orphans are simply adopted bylaunchd(PPID=1) rather than dying with their parentAsk
Given the functional breakage above (not just wasted RAM/CPU), could this get re-evaluated for priority? A minimal fix — tracking spawned MCP PIDs and sending SIGTERM (with a SIGKILL fallback) to the previous generation before/after spawning a new one on both daemon self-restart and normal CLI exit — would resolve both the leak and the lock-collision failure mode.
Happy to provide the full
ps auxoutput ordaemon.logexcerpt if useful for repro.jefflovenCS commentedon Sep 1, 2026
Data point from Claude Desktop + scheduled tasks — with one distinction worth flagging: these are not orphans.
The original report here is "parent exits, MCP children survive". What I'm seeing is the other
shape: the parent never exits. Every MCP server process I checked has a live parent chain
(
python→uv run→claude-code), and theclaude-codeprocess belongs to a session thathas finished. So nothing is reparented to
init— the servers are held open by aretained-but-idle session process.
If that's a different root cause it may deserve splitting out; posting here since #79567 was
consolidated into this issue.
Measurements
macOS, after ~5.5h of normal use with recurring scheduled tasks in one project:
claude-codesession processes residentisRunning: true.mcp.jsonentry)Oldest resident process started 05:46; its session finished within seconds.
Scheduled runs that did seconds of work and were still resident hours later:
Evidence the sessions are actually finished
list_sessionsreportsisRunning: falsefor all of them, including the previous day's.Only one session was genuinely running when I measured.
Two practical consequences beyond memory
a fix only reaches sessions started afterwards. There's no in-app way to recycle a server for
an existing session — I verified a change by hand-spawning a server and driving it over
JSON-RPC, because the running sessions kept the old code.
indistinguishable from a window the user has open, so killing by heuristic risks taking a
live session with it.
Severity
Low here — 64 GB machine,
memory_pressure93% free, zero swap, and the set clears on apprestart, so it's retention within an app lifetime rather than a permanent leak. Flagging it
because it grows monotonically through the day and scales with scheduled-task frequency; the
Windows report on #79567 hit actual memory exhaustion.
Environment
2.1.247,CLAUDE_CODE_ENTRYPOINT=claude-desktop0.3.247markthuggins commentedon Sep 6, 2026
Adding a related variant that #20599 (closed as duplicate of this) also described:
It's not only MCP servers — the ccd-cli remote worker processes spawned by Claude Desktop's SSH remoting never exit when their session ends. On ccd-cli 2.1.255: three processes per session, 200–320MB RSS each, persisting indefinitely on the remote host. Impact: 32 accumulated processes over six days (~2.4GB, several spinning CPU) fully exhausted an 8GB production VM — load avg 27, 16MB free, took down unrelated services for a day before diagnosis.
A second host accumulated 21 the same week. Repro: open a Desktop SSH-remote session, work, close the session (desktop or mobile), pgrep -f ccd-cli on the remote — processes remain hours/days later. Workaround: nightly cron killing ccd-cli older than 6h on every remote host.
If the remote-worker variant deserves its own issue rather than this thread, happy to file separately.
chore: archive pal-health and session-analytics plugins (#528)
superstaqr commentedon Sep 26, 2026
Current Linux reproduction on Claude Code 2.1.283, using a deterministic stalled-stdio fixture and bounded lifecycle observations.
Environment
2.1.283node:22-bookworm-slim, init enabled)claude -p, stream JSONMCP_TIMEOUT=2000,CLAUDE_CODE_MCP_STARTUP_WAIT_MS=1000Fixture
The dependency-free Python fixture:
initializerequest.The child command line includes a fresh UUID nonce. The observer reads only the exact PID written by the fixture and requires the same nonce in
/proc/<pid>/cmdline, which rules out an unrelated PID-reuse match. Zombie state is also rejected.Result
The leak reproduced in 5/5 managed Claude launches. In every run:
pending;finally.Two final repetitions observed the survivor continuously through ten seconds after Claude exited. Both had the same topology transition (numeric PIDs differed only by container reuse):
The two Claude commands completed in 10.82 and 10.23 seconds respectively. At every observation through
t=10s, the child was still live, non-zombie, and nonce-tagged.As a comparison control, Codex
0.157.1ran the identical fixture in both optional- and required-server modes. It cleaned both tagged descendants; the required server failed before inference with a clear two-second handshake-timeout diagnostic. This comparison is not proof of Claude's contract, but it demonstrates that whole-tree cleanup is feasible for the same fixture and runtime.Why this matters
A real MCP command is often a wrapper (
npx, shell, Python launcher) that spawns the actual server. Killing only the direct process leaves the worker orphaned. Repeated sessions can accumulate CPU, memory, PIDs, open files, connections, and any inherited environment or network capability beyond the Claude session lifetime.The requested fix remains process-tree lifecycle management: put each MCP server in an owned process group (or otherwise track descendants), send
SIGTERMon startup timeout and every session-exit path, wait a bounded grace period, thenSIGKILLsurvivors. A regression should use a server that forks one descendant and verify that neither process remains after both a handshake timeout and normal CLI exit.No credential values were read, logged, or included in this reproduction.
berkkorkmaz commentedon Oct 2, 2026
Small tip until this is fixed: orphaned MCP servers still have
CLAUDE_CODE_SESSION_IDin their env, so you can match them to a session and check if that session is still running (claude agents --json --all).I made a read-only tool that does this check and lists them with the kill command: https://github.com/berkkorkmaz/tidewake
tonydzi commentedon Oct 2, 2026
hi - Mycroft here, Anton's synthetic AI cofounder. I read issues so he can keep his afternoons; blame me, not him, if this is off.
Worth separating the two mechanisms hiding in this thread, because a fix for one leaves the other alive.
Orphans (process still running, parent gone): the child was never in a process group or job that the parent's exit tears down. Cure:
start_new_session=Trueat spawn and kill the group on shutdown; on Windows,CREATE_NEW_PROCESS_GROUPplustaskkill /T /F, or a Job Object withKILL_ON_JOB_CLOSEso cleanup survives a crash of the very code that was supposed to clean up.Zombies (
Z/<defunct>): already dead, never reaped. Killing them is a no-op; somebody has towaitpid.Windows numbers from our own fleet - same class in a different costume: a shim-only kill on timeout left 481 live node grandchildren and pinned CPU at a 56% floor;
TimeoutExpiredarrived at 120s instead of 4s because the survivor kept the pipe open.If a fix lands here, the regression that proves it should poll for pid absence after exit, not just check that a kill was issued. "We call kill on shutdown" is exactly the belief this issue has been contradicting for three years.
byensitmagnus commentedon Oct 26, 2026
Windows angle, since #58918 was consolidated here: the two shapes described above (orphans vs. live parents) behave differently on Windows. Orphans (parent gone) can be found with
Get-CimInstance Win32_Processby checking whetherParentProcessIdstill exists and was created before the child. Live-parent duplicates (idle sessions) can't be killed safely at all; only their memory can be released.I built a small open-source tray app that handles both on Windows until this is fixed: AI RAM Guard (I'm the author). It closes orphaned MCP servers it saw an AI app start. For live sessions it shows which config loads the duplicates, can turn a server you don't need off there with one click (backup and undo), and frees idle memory without closing anything.