Monitor until-loop leaks a shell that spins indefinitely when its exit condition never matches (survives task purge & /compact)

Resolved 💬 2 comments Opened Jun 30, 2026 by np-we360 Closed Jul 4, 2026

What happened

A Monitor started with a polling until-loop never exited because its grep condition never matched, leaving a shell spinning sleep 3 for 10h+. The shell counter (N shell still running) was stuck at 1 and could not be reconciled or stopped programmatically.

The Monitor's command (from the Shell details view):

f=…/tasks/a2213d84c8e8a4938.output
until [ -f "$f" ] && grep -q '"type":"result"' "$f" 2>/dev/null; do sleep 3; done; echo done

It was watching an agent's .output file for a "type":"result" marker. The agent completed and its task was purged, but the .output never contained that literal string, so the loop spun forever.

Shell details showed: Status: running · Runtime: 10h 2m 27s.

Expected

A Monitor whose watched task/file no longer exists (purged), or that exceeds its timeout_ms, should self-terminate and decrement the running-shell counter.

Actual

  • The loop spins sleep 3 indefinitely (observed 10h+).
  • It's invisible to a process scan filtered on pytest|uv|python (it's a bash sleep-loop), so it's easy to misdiagnose as "nothing running."
  • The assistant cannot stop it: TaskStop/TaskOutput on the watched id returns No task found with ID: … (purged), and the Monitor's own task id is not surfaced to the model — only the user can kill it via ↓ to manage → x.
  • It persisted across a /compact.

Repro (approx.)

  1. Start a Monitor with a polling until-loop whose exit condition may never match (e.g. grepping an agent transcript for a string that isn't written in that form).
  2. Let the watched task complete and get purged.
  3. Observe the Monitor keeps running indefinitely; the shell counter stays at 1; querying the watched id yields "No task found"; a pytest|python ps scan shows nothing.

Suggested fixes

  • Reap Monitors whose watched task/file no longer exists, or whose timeout_ms has elapsed.
  • Surface the Monitor's own task id to the model so it can be TaskStop-ped programmatically.
  • (Docs/guardrail) Discourage Monitor polling-loops for awaiting agents — agent completions are delivered via notifications.

Environment

  • Claude Code 2.1.177
  • macOS (Darwin 24.6.0), zsh
  • Long session (~577k tokens), post-/compact, auto mode on

View original on GitHub ↗

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