Scheduled tasks and background sub-agents leak processes/UI state without cleanup
Environment
- Claude Code: 2.1.119
- OS: Windows 11 Pro (10.0.26200) — observed on Windows; lifecycle/cleanup logic likely platform-agnostic
- Shell: Git Bash + PowerShell
---
Summary
A scheduled task or background sub-agent that completes its work successfully often does not exit cleanly. The harness lacks per-tool wall-clock timeouts, never SIGKILLs hung children, and leaves stale UI state with no way to clear it. Over a week these leaks accumulate to ~1.5 GB of zombied RAM and a Tasks panel cluttered with phantom "Running" entries.
---
Symptom 1 — Scheduled-task processes zombify after completing their work
A daily scheduled task (scheduled-tasks MCP, fires at 08:08) writes its output file successfully, but the underlying claude-code\2.1.119\claude.exe --output-format stream-json process never exits. After 4 days I had 4 zombied processes from the same daily task, each holding 200–600 MB resident.
A separate failure mode: one day the scheduled task hung mid-run (an MCP tool call apparently never returned). It produced no output at all and ran for 24+ hours. There is no per-tool wall-clock timeout to catch this — the child sits forever waiting on stdin from a parent harness that has long since lost interest.
Reproducer: create a scheduled task that calls a moderate set of MCP tools (the more servers in the chain, the higher the chance one stalls). After ~1 week, list claude.exe processes — expect to see one extra zombie per successful run, plus any hung mid-run.
---
Symptom 2 — Background bash tasks remain "Running" in the Tasks panel after their OS processes exit
I spawned a background sub-agent via the Agent tool (run_in_background: true) for a 10-minute pure-local audit. The sub-agent itself completed cleanly and emitted its <status>completed</status> notification. But during execution it had spawned several nested background bash commands (e.g. until [ -f flag ]; do sleep N; done polling loops). After the parent agent completed:
- ✅ All
bash.exe/sh.exeOS processes had exited (verified viaGet-CimInstance Win32_Process) - ❌ Five entries remained in the Tasks panel labeled "Running" indefinitely
Symptom 3 — No working path to clear the phantom entries
| Path | Result |
|---|---|
| Click X on the entry in the Tasks panel | No effect — entries persist |
| Call TaskStop with task ID derived from ~/AppData/Local/Temp/claude/<session>/tasks/<id>.output | Returns No task found with ID: <id> |
| Restart Claude Code | Works (clears panel) |
Whatever ID format the panel uses internally is not reachable from either the UI's own dismiss control or from TaskStop. Restart is the only escape.
---
Suggested fixes (in priority order)
- Per-tool wall-clock budget enforced by harness. If a single tool call doesn't return within N seconds (configurable, default ~120s), the harness should mark the call failed and feed an error back to the agent rather than letting the child sit forever on stdin. This alone would fix the silent-hang case.
- Hard exit-after-completion for scheduled tasks. When a
--output-format stream-json --resume <task-session-id>child emits its terminal turn message and stdin closes, the harness should SIGKILL after a short grace period instead of leaving the child alive.
- Tasks panel state should track underlying process exit. When a background bash task's OS process terminates, its panel entry should flip to "Completed" / "Failed" within seconds, not stay "Running" forever.
- Either fix the X button or align
TaskStop's ID lookup with the panel's IDs. Right now the user has no in-session escape from a stuck panel entry.
---
Evidence available on request: PowerShell Get-CimInstance output of zombied processes by age/RAM, full command lines showing the headless flags (--output-format stream-json --verbose --input-format stream-json --model default --permission-prompt-tool stdio --allowedTools mcp__computer-use,...), the smoke-test agent's transcript showing clean completion, and the temp-dir task ID file listing whose IDs TaskStop rejects.
This issue has 6 comments on GitHub. Read the full discussion on GitHub ↗