Skip to content

MCP server processes are never cleaned up when sessions end — orphan accumulation causes OOM #26658

@CyPack

Description

@CyPack

Problem

When a Claude Code session ends (exit, crash, terminal close, or kill), the MCP server child processes it spawned are never terminated. They persist as orphan processes indefinitely, accumulating across sessions until the system runs out of memory.

Each session spawns ~10 MCP server processes (playwright-mcp, chrome-devtools-mcp, dokploy-mcp, context7-mcp, sequential-thinking, ollama-mcp, arxiv-mcp-server, etc.). After a few days of normal usage, this leads to dozens of orphaned MCP processes consuming multiple GB of swap/RAM.

Evidence

System with 10 configured MCP servers, after ~3 days of normal usage:

=== Swap consumers by category ===
     6517 MB  claude          (10+ stale sessions)
     4023 MB  MainThread      (orphaned MCP servers: playwright, chrome-devtools, dokploy...)
     3000 MB  npm             (orphaned npm exec MCP starters)

Total orphaned: ~13 GB swap from ~80 processes
OOM kills in 24 hours: 1625

Process tree showing orphaned MCP servers (ppid=1, parent claude dead):

PID=3034  PPID=2927  node playwright-mcp          # parent npm dead
PID=3364  PPID=3261  node chrome-devtools-mcp      # parent npm dead
PID=3498  PPID=3427  node dokploy-mcp              # parent npm dead
PID=58073 PPID=57821 node playwright-mcp           # another session's orphans
PID=58305 PPID=58238 node chrome-devtools-mcp
...
(~80 orphaned processes total from ~8 dead sessions)

Root Cause

Claude Code spawns MCP servers as child processes but has no cleanup mechanism when the session ends:

  1. Normal exit (/exit, Ctrl+C): No child process cleanup is performed
  2. Terminal close: Claude receives SIGHUP but doesn't forward it to MCP children
  3. Kill/crash: Children get reparented to init (ppid=1) and run forever
  4. Stale sessions: Claude process itself may survive terminal close (zombie in swap) while still holding MCP children alive

The process chain is: claude -> npm exec @playwright/mcp -> node playwright-mcp
When claude dies, both npm exec and node survive as orphans.

Expected Behavior

When a Claude Code session ends (for any reason), all MCP server processes it spawned should be terminated.

Suggested Solutions

1. Process Group cleanup (simplest)

Set MCP server processes in the same process group as the claude session, then killpg() on exit:

# When spawning MCP servers
os.setpgrp()  # or use preexec_fn in subprocess

# On session exit
os.killpg(os.getpgrp(), signal.SIGTERM)

2. PR_SET_PDEATHSIG (Linux)

Set child processes to receive SIGTERM when their parent dies:

import ctypes
def set_pdeathsig():
    libc = ctypes.CDLL("libc.so.6")
    PR_SET_PDEATHSIG = 1
    libc.prctl(PR_SET_PDEATHSIG, signal.SIGTERM)

subprocess.Popen(cmd, preexec_fn=set_pdeathsig)

3. Explicit cleanup in session lifecycle

Track spawned MCP PIDs and terminate them in the session end handler:

// On session end
for (const mcpProcess of this.mcpProcesses) {
  mcpProcess.kill('SIGTERM');
}

4. PID file / heartbeat

MCP servers periodically check if their parent claude process is still alive:

setInterval(() => {
  try { process.kill(parentPid, 0); }
  catch { process.exit(0); }  // parent dead, self-terminate
}, 30000);

Current Workaround

I've implemented a two-layer workaround:

  1. SessionEnd hook (session-cleanup.sh): Kills child processes on clean exit
  2. systemd timer watchdog: Runs every 30 min, detects orphaned MCP servers (no claude ancestor in process tree) and stale sessions (dead TTY or high swap + idle)

Environment

  • Claude Code: latest (claude-code CLI)
  • OS: Fedora 43 (Linux 6.18.7)
  • 10 MCP servers configured (playwright, chrome-devtools, dokploy, context7, sequential-thinking, ollama, arxiv, scrapling, T4F, voorinfra + more)
  • 16 GB RAM, zram swap

Metadata

Metadata

Assignees

No one assigned

    Labels

    duplicateThis issue or pull request already exists

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions