Bun runtime SIGKILL's stdio MCP server process group after ~60s idle
Summary
Claude Code's Bun runtime kills stdio MCP server child processes (entire process group) with SIGKILL after approximately 60 seconds of idle time. This affects all plugin MCP servers (confirmed with context-mode) and makes them permanently unusable within any session.
Environment
- Claude Code: installed via npm (
@anthropic-ai/claude-code) - Binary:
claude.exe(Bun-compiled ELF, not Node.js) - Platform: Linux (Debian 12 container on CentOS 7 host, Docker)
- Node.js: v20.18.0
- Bun: 1.3.13
- Plugin: context-mode v1.0.120 (also reproduced on v1.0.111)
Reproduction
- Start a Claude Code session with any stdio MCP server plugin
- The MCP server starts, handles
initializeandtools/listsuccessfully - After ~60 seconds of idle time, the server process is killed
- All subsequent MCP tool calls fail with
MCP error -32000: Connection closed - The server is never respawned (stdio servers have no auto-reconnect per #45146)
Diagnostic Evidence
1. Consistent 60-second death
Stats from multiple process instances show identical ~60s uptime:
| PID | Version | Uptime (ms) | Tool calls |
|-----|---------|-------------|------------|
| 610625 | 1.0.111 | 60,055 | 0 |
| 674793 | 1.0.111 | 61,535 | 0 |
| 683372 | 1.0.111 | 70,656 | 1 |
| 901017 | 1.0.120 | 60,060 | 0 |
2. SIGKILL confirmed — no Node.js handlers fire
Added signal/exit instrumentation to start.mjs:
for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"])
process.on(sig, () => appendFileSync(log, `SIGNAL: ${sig}\n`));
process.on("exit", (code) => appendFileSync(log, `EXIT code=${code}\n`));
process.stdin.on("end", () => appendFileSync(log, `STDIN_END\n`));
Result: only STARTING was logged. No signal, no exit, no stdin event. This rules out SIGINT/SIGTERM/SIGHUP (all catchable) and confirms SIGKILL (uncatchable).
3. Entire process group killed
Wrapped the MCP server in a bash script (bash → node start.mjs). The bash wrapper's wait never returned and the wrapper itself was killed simultaneously. This confirms the kill targets the process group (kill -9 -<pgid>), not just the child PID.
4. Not OOM, not cgroup, not system
dmesg: no OOM kill entries- cgroup memory limit: unlimited (9.2 EB)
- cgroup PID limit: unlimited
- No system-level process management killing node processes
5. Core dump from previous crash
A prior instance left a core dump (core.node.436164.1778421683.11, 113MB). GDB backtrace:
#0 0x12276 in ?? () ← wild pointer
#1 Addon::Cleanup(void*) in better_sqlite3.node ← native cleanup
#2 node::CleanupQueue::Drain()
#3 node::Environment::RunCleanup()
#4 node::FreeEnvironment(node::Environment*)
The SIGSEGV happens in better-sqlite3's cleanup handler during process exit — a secondary bug triggered by the primary forced shutdown.
Root Cause Hypothesis
The claude.exe binary (Bun-compiled) uses StdioClientTransport from @modelcontextprotocol/sdk to spawn MCP servers. After ~60 seconds idle (possibly related to MCP_REQUEST_TIMEOUT_MS = 60000 in src/services/mcp/client.ts), the Bun runtime kills the child's entire process group with SIGKILL — bypassing the graceful SIGINT→SIGTERM→SIGKILL escalation path implemented in client.ts:1430-1540.
Related Issues
- #45146 — MCP servers don't respawn after process death (stdio has no reconnect)
- #57971 — Bun child receives SIGTERM every few minutes in plugin context
- mksglu/context-mode#471 — Originally attributed to context-mode's stdin listener; later retracted as "stdin is being closed by Claude CLI"
Expected Behavior
Stdio MCP server processes should remain alive for the entire session duration, not be killed after 60 seconds of idle time.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗