Scheduled-task runner leaks finished stream-json sessions (never closes stdin / signals exit) → CPU exhaustion
What happened
A scheduled task (cron routine) firing every 20 minutes accumulated 136 idleclaude agent-mode processes over ~21 hours, driving system load to 70 and
CPU to ~85% (66% in kernel). Each leaked process holds a full MCP stack
(computer-use, markdown-vault, plugin MCPs), so the pile-up is heavy.
Root cause is a teardown bug in the scheduled-task ("rpm") runner: it launches
each routine as a persistent stream-json session and never reaps it after the
turn completes.
Diagnosis
The routine runner spawns sessions in persistent interactive mode:
claude --output-format stream-json --input-format stream-json \
--replay-user-messages --resume <id> ... (full plugin/MCP stack)
A stream-json session only exits on stdin EOF or a signal. After the
scheduled turn finishes, neither happens:
- stdin stays open, held by the desktop app. The leaked session's fd 0 is a
unix socket whose peer is owned by the Claude desktop app process — confirmed
via lsof. The app never closes it.
- The turn finished. Example process: 5m38s wall clock, 6.97s CPU total,
then idle.
- Idle in the event loop, not stuck working. Main thread parked in
kevent64 (libuv waiting for events). All leaked sessions show the identical park.
- MCP children keep handles alive.
markdown-vault-mcpand plugin MCP
servers remain attached as live stdio handles, so Node has no reason to exit.
Every 20-minute fire adds one more idle event-loop + MCP stack.
Proof it's the runner, not the routine's work
The routine's own sub-agents are launched one-shot
(claude -p ... --no-session-persistence) and exit cleanly every time. Only
the runner sessions the app spawns in persistent mode leak. The bug is in the
spawner's completion handling.
Expected
After a scheduled-task turn completes, the runner should close the session's
stdin (or send SIGTERM) so the process and its MCP children exit.
Suggested fix
Either:
- Launch routines in
-p/--no-session-persistenceone-shot mode (matches
the sub-agent path that already exits cleanly), or
- Close stdin / signal the session once the final result is received.
Environment
- Claude Code 2.1.187 (desktop app)
- macOS (Darwin 25.5.0), Apple Silicon
- Scheduled task cadence:
*/20 * * * *
3 Comments
Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
Hi, this will be fixed in the next release, thank you for reporting.
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.