Scheduled-task runner leaks claude-code subprocesses (build 2.1.121)
Bug — Claude Desktop scheduled-task runner leaks claude-code subprocesses
Summary
The local-agent-mode scheduled-task runner in Claude Desktop 1.5354.0 (using bundled claude-code/2.1.121) does not tear down spawned runner subprocesses after a scheduled task completes. Each fire leaks one disclaimer wrapper + one claude child, both stuck in interruptible sleep (S) on stdio. After ~24 hours of normal cron activity (~7 fires/hr), ~200 stuck pairs accumulate. Once the renderer's internal subprocess cap is reached, the dispatcher silently swallows further fires — nextRunAt advances past missed slots without spawning a runner.
Repro
- Configure any local-agent-mode scheduled task with a frequent cron (e.g. every 15 min).
- Leave Claude Desktop running for 24h.
- Observe
ps -ax | grep 'claude-code/2.1.121/claude.app/Contents/MacOS/claude' | wc -lclimbing monotonically. - After enough accumulation, scheduled tasks stop firing entirely while the desktop UI shows no error.
The bug reproduces independently of:
- Which model the task uses (observed across
claude-opus-4-7,claude-sonnet-4-6,claude-sonnet-4-5-20250929,default) - Permission mode (observed across
bypassPermissionsanddefault) - The skill content (no skills in the affected setup spawn background processes; verified by
grep -rE 'nohup|disown|setsid|& *$' ~/.claude/scheduled-tasks/) - User-configured hooks (no
Stop/PostToolUsehook configured)
Process signature
Each leaked pair has:
Outer (disclaimer wrapper):
- argv0:
/Applications/Claude.app/Contents/Helpers/disclaimer - argv: includes the full path to
claudeand all its flags - ppid:
Claude.app/Contents/MacOS/Claude(main process)
Inner (claude runner):
- argv0:
/Users/<user>/Library/Application Support/Claude/claude-code/2.1.121/claude.app/Contents/MacOS/claude - ppid: the outer disclaimer process
- relevant flags:
--output-format stream-json --input-format stream-json --permission-prompt-tool stdio --allow-dangerously-skip-permissions --include-partial-messages --replay-user-messages --settings {} - state:
S(interruptible sleep) - RSS: ~80 MB, stable
The combination of --input-format stream-json and --permission-prompt-tool stdio means the child reads from stdin until EOF. The hang signature is consistent with the parent (Claude Helper Renderer hosting the local-agent-mode runner) failing to close the child's stdin pipe when the skill's Stop event fires.
Environment
- Claude Desktop version:
1.5354.0 - claude-code build:
2.1.121(bundled in Claude.app) - macOS: 25.1.0 (Darwin)
- Scheduled-tasks feature:
ccdScheduledTasksEnabled: true+coworkScheduledTasksEnabled: true
Impact
- All scheduled tasks silently stop firing after the cap is hit
- No error surfaces in the Claude Desktop UI, log files, or notifications
- Recovery requires manually killing the entire pile (
pkill -f claude-code/2.1.121) — no in-app reset - The watchdog/heartbeat task pattern Anthropic users rely on (e.g. a daily
cron-heartbeattask that pings if any other task is stale) doesn't catch this because the heartbeat itself is one of the silently-skipped tasks
Workaround in place locally
A janitor launchd agent (com.alex.argus.claude-janitor, every 10 min) reaps claude-code/2.1.121 processes whose parent's command contains Claude.app AND whose elapsed time exceeds 30 min. Hard-coded to build 2.1.121 so it auto-no-ops once a fixed build ships. Source available on request.
What would help
- Fix: have the runner explicitly close child stdio (or
kill -9the child) when the skill'sStopevent fires — preferably with a 30-second hard-deadline regardless of pipe state. - Telemetry: surface a UI warning when pending child-runner count exceeds, say, 20. Right now the failure is invisible until the user manually
ps-greps. - Recovery: an in-app "Restart scheduled-tasks runner" button or auto-reaper for orphaned local-agent-mode subprocesses older than 30 min.
Local diagnosis report
Full investigation, evidence, and ruled-out hypotheses:~/Documents/Claude by Alex/Reports/2026-05-01 stuck-cron-process-diagnosis.md
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗