Skip to content

doctor: detect orphaned CC-spawned processes and MCP server leak (root cause behind #3180 and #3190) #3198

Description

@yonatangross

Problem

Long-lived Claude Code work on this machine accumulates orphaned CC-spawned
processes until new sessions stall on startup. Measured 2026-07-28, the
machine was carrying:

claude CLI sessions .......... 16
MCP server processes ......... 368     .mcp.json declares 9
                                       16 x 9 = 144 expected
                                       368 actual = 2.5x leak
total hung (>30m, <5s CPU) ... 841

A targeted sweep reclaimed 313 processes, all on SIGTERM, zero needed
SIGKILL
, which is the tell that they were idle wrappers and not busy work.

                    BEFORE      AFTER      DELTA
MCP processes         328    →     82      -246   -75%
total processes     1,387    →  1,070      -317
load (1m)            6.30    →   5.97
hung ork eval test      4    →      0
hung CC hooks          ~6    →      0

Duplicated servers were part of it: both mcp-server-memory and
server-memory variants were leaking, and context7 had two variants too.

Why this is an ork issue, not just machine hygiene

It is the shared root cause behind at least two open bug reports that each look
like an independent hang:

Both carry the same signature: sleeping indefinitely at essentially zero CPU.
Run standalone on an idle machine, both complete normally (the build finished in
35 seconds; the guard in under 30 seconds, rc=0, 14 passed).

The mechanism that fits every observed case: a child orphaned when its parent
session dies keeps writing to a pipe nobody drains, and blocks forever once the
64 KB buffer fills.

bash tests/evals/test-dead-generation-guard.sh   0:00.00 cpu · 20h, 27h
bash ~/.claude/hooks/session-start-context.sh    0:00.00 cpu · 18h
bash ~/.claude/hooks/cmux-ci-pills.sh            0:00.01 cpu · 18h
bash scripts/build-plugins.sh                    0:00.22 cpu · 6h28m

Four unrelated scripts, each healthy in isolation. That is one condition, not
four bugs. Fixing #3180 and #3190 individually would add timeouts to two of
these and leave the other two exposed.

Proposal

Add an orphan-process health check to doctor:

  1. Count live claude --session-id processes and MCP server processes.
  2. Compare against declared servers x live sessions. Report the ratio.
  3. List CC-spawned processes over a threshold age with near-zero cumulative CPU.
  4. Report only. Do not offer to kill from inside a hook.

⚠️ Targeting is the hard part, and the naive filter is dangerous

A first attempt used "older than 30 minutes and under 5s CPU" against all
processes. That heuristic describes every well-behaved background app: the
dry run caught Slack, Wispr Flow, Docker vmnetd, Core Audio Driver and autofsd,
and its own safety assertion reported 38 live claude CLIs inside the kill list.
It was never piped to kill.

Two further display bugs made the dry run look wrong when the targeting was
actually fine:

  • simple x88 was a mislabel. Those were
    uv tool uvx --index .../simple --from yg-mcp-*, i.e. genuine servers. A
    sed 's|.*/||' had grabbed a word out of a URL.
  • "74 GUI/system processes" came from grepping /Library/ unanchored, which
    matches ~/Library/pnpm/... node binaries.

The check must therefore target by process family (MCP server command
patterns, CC hook and test shells), never by age plus idleness alone, and must
anchor its paths. The verification regex has to be identical to the targeting
regex: a first pass omitted the modelcontextprotocol literal and reported 6
false unknowns.

Acceptance

  • doctor reports the MCP-server-to-session ratio and flags a leak above a
    documented threshold.
  • The check asserts zero live claude --session-id processes in anything it
    labels reclaimable.
  • Path matching is anchored; targeting and verification share one pattern.
  • Findings are reported, never auto-killed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions