Heroku MCP server spawns persistent --repl processes consuming 90-140% CPU even when never invoked
Description
The Heroku MCP server (@heroku/mcp-server, bundled with Heroku CLI via Homebrew) spawns npm exec heroku@latest --repl processes that consume 90-140% CPU each even when the MCP tools are never called during a session.
Environment
- Claude Code: v2.1.73
- Heroku CLI: 11.0.0 (Homebrew
/opt/homebrew/Cellar/heroku/10.17.0/) @heroku/mcp-server: v1.2.0 (bundled with CLI)- OS: macOS Darwin 25.3.0 (Apple Silicon)
- Node: v24.13.1
Steps to Reproduce
- Have Heroku CLI installed via Homebrew (includes
@heroku/mcp-server) - Start a Claude Code session in any project with
mcp__heroku__*in permissions - Do NOT call any Heroku MCP tools — use other tools only
- Observe CPU usage:
ps -eo pid,pcpu,rss,command | grep heroku
Observed Behavior
Each Claude Code session spawns a persistent process tree:
claude (session)
└── bash heroku mcp:start
└── node heroku-mcp-server.mjs
└── npm exec heroku@latest --repl ← 90-140% CPU, 170-210 MB RSS
└── node heroku --repl ← additional CPU
Key findings from diagnostics:
| Metric | Value |
|--------|-------|
| CPU per --repl process | 90-140% (single core) |
| RAM per process | 170-210 MB |
| Processes per session | 2-4 (npm + node children) |
| Total CPU for 3 old sessions | 450%+ CPU (heating laptop significantly) |
| Behavior when never called | Same — REPL runs idle event loop consuming full CPU |
Accumulation across sessions
When sessions are closed, MCP child processes are not terminated (related: #1935). Over 2-5 days of normal usage, we observed:
- 8 Claude sessions running simultaneously (user forgot to close terminals)
- ~100+ zombie MCP processes across all sessions
- 4+ Heroku REPL processes from dead sessions, including orphans (PPID=1)
- Total: 2.2 GB RAM + 450% CPU from Heroku MCP alone
Process tree trace (from live diagnostics)
PID 62794 npm exec heroku@latest --repl CPU=31% RSS=206MB
↑ parent: node heroku-mcp-server.mjs
↑ parent: heroku mcp:start
↑ parent: claude --session-id 2f5a1d2a (4.5 hours old, heroku never used)
PID 62810 npm exec heroku@latest --repl CPU=146% RSS=204MB
↑ parent: heroku mcp:start
↑ parent: PID 1 (orphan — parent session already dead)
PID 62802 npm exec heroku@latest --repl CPU=94% RSS=208MB
↑ parent: heroku-mcp-server.mjs
↑ parent: claude --session-id bdf6f946 (current session, heroku NEVER called)
Expected Behavior
- MCP server should not consume significant CPU when idle (REPL event loop should sleep/park)
- MCP processes should be terminated when the parent Claude Code session exits
- Ideally: MCP server should lazy-start only when its tools are first invoked, not at session start
Root Cause Analysis
Two contributing factors:
- Heroku MCP design:
--replmode maintains a persistent interactive CLI session (documented as intentional for performance). However, the REPL's idle event loop consumes full CPU instead of parking/sleeping.
- Claude Code lifecycle: MCP child processes are not killed on session close (#1935). This turns the CPU issue into an accumulation problem where each unclosed session permanently adds 90-140% CPU load.
Current Workaround
Added a Stop hook in ~/.claude/settings.json:
{
"hooks": {
"Stop": [{
"hooks": [{
"type": "command",
"command": "pkill -9 -f 'npm exec heroku' 2>/dev/null; pkill -9 -f 'heroku.*--repl' 2>/dev/null; pkill -9 -f 'heroku-mcp-server' 2>/dev/null; true",
"timeout": 5
}]
}]
}
}
This catches ~80% of cases but doesn't help when sessions are terminated abnormally.
Related Issues
- #1935 — MCP servers not properly terminated when Claude Code exits
- #15861 — MCP processes accumulate without cleanup
- #28982 —
claude mcp removedoesn't kill background processes - #4666 — 300-400% cost inflation from MCP process multiplication
- #22968 — High CPU usage and memory leak in long-running sessions
Suggested Fixes
- Claude Code: Implement proper MCP process lifecycle — kill child process tree on session end (addresses #1935)
- Claude Code: Lazy-start MCP servers only when their tools are first invoked (not at session start)
- Heroku MCP: REPL idle loop should use
setTimeout/setImmediatewith appropriate delays, not spin the event loop at 100% CPU - Heroku MCP: Add idle timeout configuration (
HEROKU_MCP_IDLE_TIMEOUT_SECONDS) to auto-exit after period of inactivity
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗