claude-code - 💡(How to fix) Fix Claude Desktop: orphaned MCP subprocesses accumulate after 4-min tool-call timeout [2 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#52859Fetched 2026-04-25 06:18:58
View on GitHub
Comments
2
Participants
2
Timeline
3
Reactions
0
Author
Participants
Timeline (top)
commented ×2labeled ×1

Claude Desktop does not reap stdio-based MCP server subprocesses when a tool call hits the hardcoded 4-minute timeout. On retry, a fresh subprocess is spawned without cleaning up the previous one. Over time, orphaned processes accumulate and eventually exhaust resources, at which point any MCP tool call hangs until the user fully quits and restarts Desktop.

Error Message

send({"jsonrpc":"2.0","id":mid,"error":{"code":-32601,"message":"not found"}})

Root Cause

Two possible root causes, either would explain the observed pattern:

Fix Action

Fix / Workaround

FolderWhat
01-watchdog-logs/23 orphan-kill events with PID + age + timestamp
02-claude-desktop-mcp-logs/Desktop's MCP log (sanitized — phone numbers and content strings redacted) + main.log MCP-related excerpt
03-watchdog-config/launchd plist + shell script of the workaround
04-minimal-repro/Self-contained Python hang-server + step-by-step repro instructions
05-cross-client-ab/Extracted WhatsApp MCP config snippets from both Code and Desktop showing identical shapes

Workaround currently in use

Code Example

#!/usr/bin/env python3
import sys, json, time

def send(msg):
    sys.stdout.write(json.dumps(msg) + "\n"); sys.stdout.flush()

for line in sys.stdin:
    try: msg = json.loads(line)
    except: continue
    mid, method = msg.get("id"), msg.get("method", "")
    if method == "initialize":
        send({"jsonrpc":"2.0","id":mid,"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{}},"serverInfo":{"name":"hang-repro","version":"1.0"}}})
    elif method == "tools/list":
        send({"jsonrpc":"2.0","id":mid,"result":{"tools":[{"name":"trigger_hang","description":"Deliberately blocks","inputSchema":{"type":"object","properties":{}}}]}})
    elif method == "tools/call":
        time.sleep(600)   # block — this is the point
    elif method == "notifications/initialized":
        pass
    else:
        send({"jsonrpc":"2.0","id":mid,"error":{"code":-32601,"message":"not found"}})

---

{
  "mcpServers": {
    "hang-repro": {
      "command": "/usr/bin/python3",
      "args": ["/absolute/path/to/hang-server.py"]
    }
  }
}

---

pids=$(pgrep -f "whatsapp-mcp-server/.venv/bin/python3 main.py")
for pid in $pids; do
    age=$(( $(date +%s) - $(ps -o lstart= -p "$pid" | xargs -I{} date -j -f "%a %b %d %T %Y" {} +%s) ))
    [ "$age" -gt 900 ] && kill -9 "$pid"
done
RAW_BUFFERClick to expand / collapse

Filed at request of Anthropic support (ticket reference on file) who confirmed this is a known Claude Desktop issue and asked for the diagnostic materials to be submitted publicly. This affects Claude Desktop specifically — Claude Code on the same machine does not exhibit the behavior. I realize this tracker is primarily for Claude Code; support redirected me here as the official public intake. Happy to move if there's a dedicated Desktop tracker I missed.

Summary

Claude Desktop does not reap stdio-based MCP server subprocesses when a tool call hits the hardcoded 4-minute timeout. On retry, a fresh subprocess is spawned without cleaning up the previous one. Over time, orphaned processes accumulate and eventually exhaust resources, at which point any MCP tool call hangs until the user fully quits and restarts Desktop.

Environment

  • macOS (Apple Silicon), Claude Desktop latest stable
  • Same machine also runs Claude Code; both clients configured to use the same MCP server binary via identical config shape
  • Example MCP server: verygoodplugins/whatsapp-mcp — Python stdio server
  • Reproducible with any blocking stdio MCP server (see minimal repro below, no WhatsApp dependency)

Reproduction

Minimal repro (no external dependencies)

  1. Save this as hang-server.py:
#!/usr/bin/env python3
import sys, json, time

def send(msg):
    sys.stdout.write(json.dumps(msg) + "\n"); sys.stdout.flush()

for line in sys.stdin:
    try: msg = json.loads(line)
    except: continue
    mid, method = msg.get("id"), msg.get("method", "")
    if method == "initialize":
        send({"jsonrpc":"2.0","id":mid,"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{}},"serverInfo":{"name":"hang-repro","version":"1.0"}}})
    elif method == "tools/list":
        send({"jsonrpc":"2.0","id":mid,"result":{"tools":[{"name":"trigger_hang","description":"Deliberately blocks","inputSchema":{"type":"object","properties":{}}}]}})
    elif method == "tools/call":
        time.sleep(600)   # block — this is the point
    elif method == "notifications/initialized":
        pass
    else:
        send({"jsonrpc":"2.0","id":mid,"error":{"code":-32601,"message":"not found"}})
  1. Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
  "mcpServers": {
    "hang-repro": {
      "command": "/usr/bin/python3",
      "args": ["/absolute/path/to/hang-server.py"]
    }
  }
}
  1. Fully quit & restart Claude Desktop
  2. In a new conversation: "Use the trigger_hang tool"
  3. Claude invokes the tool. Spinner appears.
  4. Open Terminal: ps aux | grep hang-server.py — one Python process, <5s old ✅
  5. Wait 4 minutes. Claude reports timeout.
  6. ps aux | grep hang-server.pyPython process is still alive, 4+ min old ❌ Expected: Desktop should have killed it.
  7. Retry the tool in the same or a new conversation.
  8. ps aux | grep hang-server.pytwo Python processes now visible. The old orphan plus a fresh one.

Each retry adds another orphan. On Intel or constrained Apple Silicon systems, ~8 accumulated orphans is enough to make any further MCP tool call hang indefinitely due to resource contention.

Evidence from real-world usage

Over 2 days of WhatsApp MCP usage, a local launchd watchdog (pkill -9 on processes matching the MCP server pattern, older than 15 minutes, every 5 minutes) logged 23 separate orphan-kill events. Without the watchdog, Desktop became unusable within hours and required a full Cmd+Q + restart cycle.

Side-by-side comparison on the same machine, same binary, same config shape:

MetricClaude CodeClaude Desktop
Successful MCP tool calls over ~1 week~100%degrades after hours as orphans accumulate
Idle Python MCP processes0-1 (clean shutdown)0-N (orphans persist across sessions)
4-min timeout events023 over 2 days
User interventionnoneCmd+Q + relaunch; or external watchdog

The Go bridge underlying the WhatsApp MCP server responds to health checks within ~16 ms; the Python MCP server starts cleanly from a terminal; SQLite has no lock conflict. The only variable that differs between working and failing behavior is the client.

Hypotheses (unconfirmed)

Two possible root causes, either would explain the observed pattern:

  1. Timeout without SIGKILL escalation. Desktop sends SIGTERM on timeout but does not wait for exit and does not escalate to SIGKILL. A Python server blocked in sys.stdin.readline() may not handle SIGTERM promptly, so the child remains alive.
  2. Retry spawns without reaping. The retry path creates a fresh subprocess without calling waitpid (or equivalent) on the previous PID. The old PID reference is dropped, leaving the OS with a running orphan.

Suggested fix direction: after SIGTERM on timeout, give the child N seconds to exit; if still alive, SIGKILL; always waitpid before spawning a retry.

Diagnostic materials

A 22 KB zip package is attached to this issue (next comment — GitHub web UI drag-drop). Structure:

FolderWhat
01-watchdog-logs/23 orphan-kill events with PID + age + timestamp
02-claude-desktop-mcp-logs/Desktop's MCP log (sanitized — phone numbers and content strings redacted) + main.log MCP-related excerpt
03-watchdog-config/launchd plist + shell script of the workaround
04-minimal-repro/Self-contained Python hang-server + step-by-step repro instructions
05-cross-client-ab/Extracted WhatsApp MCP config snippets from both Code and Desktop showing identical shapes

Privacy: logs have been sanitized before packaging (phone numbers → [PHONE]@s.whatsapp.net, content strings >20 chars → [REDACTED-]). Watchdog log contains process metadata only.

Workaround currently in use

launchd agent com.local.whatsapp-mcp-watchdog running every 5 min:

pids=$(pgrep -f "whatsapp-mcp-server/.venv/bin/python3 main.py")
for pid in $pids; do
    age=$(( $(date +%s) - $(ps -o lstart= -p "$pid" | xargs -I{} date -j -f "%a %b %d %T %Y" {} +%s) ))
    [ "$age" -gt 900 ] && kill -9 "$pid"
done

Not a real fix — keeps the machine usable while this is resolved upstream. The same pattern works for any stdio MCP server (adjust the pgrep match).

Support ticket

Anthropic support confirmed this is a known Claude Desktop issue and asked me to file the diagnostics publicly so engineering can triage. Happy to add the ticket reference if useful; also happy to provide longer log windows, unsanitized logs under NDA, or join a debug session.

extent analysis

TL;DR

The most likely fix involves modifying Claude Desktop to properly handle subprocess timeouts by sending a SIGKILL after a grace period following a SIGTERM, and ensuring that it waits for the previous process to exit before spawning a new one.

Guidance

  • Investigate the subprocess handling code in Claude Desktop to identify why it does not properly clean up after a timeout.
  • Consider implementing a mechanism to wait for a subprocess to exit after sending a SIGTERM, and if it doesn't exit within a certain timeframe, send a SIGKILL to ensure it is terminated.
  • Review the retry logic to ensure that it properly waits for the previous subprocess to exit before spawning a new one, preventing the accumulation of orphaned processes.
  • Test the minimal reproduction steps provided to understand the behavior and verify any fixes.
  • Analyze the provided diagnostic materials, including logs and the watchdog script, to gain further insight into the issue.

Example

No specific code example can be provided without access to the Claude Desktop source code, but the concept would involve modifying the subprocess handling to include a timeout and proper cleanup, similar to the workaround script provided.

Notes

The exact implementation details will depend on the specific architecture and programming language used by Claude Desktop. The provided workaround using a launchd agent demonstrates a possible approach to mitigating the issue but does not address the root cause.

Recommendation

Apply a workaround similar to the launchd agent script provided, which periodically kills orphaned processes, until a proper fix can be implemented in Claude Desktop. This will prevent the accumulation of orphaned processes and the resulting resource exhaustion.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix Claude Desktop: orphaned MCP subprocesses accumulate after 4-min tool-call timeout [2 comments, 2 participants]