Bun runtime SIGKILL's stdio MCP server process group after ~60s idle

Resolved 💬 3 comments Opened May 11, 2026 by catchmeee2002 Closed May 15, 2026

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

  1. Start a Claude Code session with any stdio MCP server plugin
  2. The MCP server starts, handles initialize and tools/list successfully
  3. After ~60 seconds of idle time, the server process is killed
  4. All subsequent MCP tool calls fail with MCP error -32000: Connection closed
  5. 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.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗