stdio MCP servers spawned via launcher commands (bun run, npx) leave orphaned grandchildren after CLI exit — consider killing the process group

Open 💬 1 comment Opened Jul 10, 2026 by elitekid

Environment

  • Claude Code 2.1.206, macOS 26.5 (Apple Silicon), bun 1.3.11
  • Headless -p sessions (stream-json) spawned/recycled by an external supervisor; plugin MCP servers from the claude-plugins-official marketplace

Summary

When an MCP server's command is a launcher rather than the server binary itself — e.g. the telegram plugin's

{ "command": "bun", "args": ["run", "--cwd", "${CLAUDE_PLUGIN_ROOT}", "--shell=bun", "--silent", "start"] }

— the CLI's shutdown only reaps its direct child (the launcher). The real server is a grandchild (bun runbun server.ts); on CLI exit (SIGTERM, crash, supervisor recycle) it reparents to PID 1 and lives on. Well-behaved servers can self-terminate on stdin EOF, but (a) many don't, and (b) even then the launcher process itself is often left behind idling.

Observed at fleet scale

On a host that recycles sessions frequently (context-compaction respawns, idle eviction, supervisor restarts) we accumulated 72 orphaned MCP pairs (144 processes, ~6.9 GB RSS) in a single day, all reparented to PID 1. Some grandchildren also ignored SIGTERM (plugin-side bug, reported separately), so only SIGKILL reclaimed them.

Expected

MCP subprocess lifetime should not depend on every plugin author getting orphan self-detection right. Suggestions, in increasing strength:

  1. Spawn each stdio MCP server in its own process group (detached/setsid) and signal the group (kill(-pgid, SIGTERM), escalate to SIGKILL after a grace period) during CLI shutdown.
  2. Do the same reaping on abnormal paths (uncaught exit, SIGTERM/SIGINT of the CLI itself).
  3. Optionally document that launcher-style commands (npx, bun run, uv run, shell wrappers) are orphan-prone if 1) is not implemented.

npx/bun run-style commands are the dominant idiom in MCP configs across the ecosystem, so the grandchild-escape pattern is common, not exotic.

Repro

  1. Register any stdio MCP whose command is a launcher that execs a child (e.g. bun run ... start, npx some-mcp)
  2. Start claude -p, confirm the process tree: claude → launcher → server
  3. kill -TERM <claude pid>
  4. ps -o pid,ppid,command — launcher and/or server remain with PPID 1

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗