Scheduled tasks and background sub-agents leak processes/UI state without cleanup

Resolved 💬 6 comments Opened Apr 29, 2026 by damschmitty Closed Jun 16, 2026

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.exe OS processes had exited (verified via Get-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)

  1. 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.
  1. 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.
  1. 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.
  1. 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.

View original on GitHub ↗

This issue has 6 comments on GitHub. Read the full discussion on GitHub ↗