[BUG] Sessions that exit with live background tasks stay "active" forever — orphan reconciliation only runs on resume
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
When a session's process exits while it still has live background tasks (Bash run_in_background, Monitors), no completion record is ever written for those tasks. The orphan reconciliation that would mark them stopped runs only when that same session is later reopened. Until then:
- the session is listed as "active" indefinitely in the desktop app's session list;
- opening its task list shows the tasks as still "running";
- the user has to hunt down and kill the leftover processes by hand.
This happens regularly in our multi-session workflow (several parallel Claude Code sessions on one repo) — closed sessions routinely linger as "active" for days.
Direct evidence — when one such session was finally reopened, this arrived at session start (verbatim):
3 background shell command task(s) from the previous session have no completion record. They may have been stopped (via the UI, Monitor timeout, or agent teardown — these leave no transcript marker), or they may have been running when the previous Claude Code process exited. They have been marked stopped.
So the harness does know how to reconcile orphans — but the scan is gated on the user resuming that exact session. A session the user considers finished (and never reopens) is never reconciled and stays "active" forever.
Two contributing behaviors we verified while debugging this:
- Background tasks have no lifetime bound. A foreground command that hit its 600 s timeout was moved to background rather than killed ("Command did not complete within its 600s timeout and was moved to the background"). A hung background task therefore lives until the OS or the user kills it — there is no TTL to cap the damage.
- Agents naturally produce immortal tasks. A common agent pattern —
until ! pgrep -f "<pattern>"; do sleep 20; doneas a completion-waiter — is a trap: one waiter is fine (pgrep excludes the caller on macOS), but **two waiters with the same pattern find each other** and neither ever exits. In one session five such waiters accumulated, all permanently "running". That's an agent-side bug, but it shows why orphaned-task leftovers are common in practice, and why an app-side TTL / reconciliation would help.
Steps to Reproduce
- In a session, start a long/never-ending background task, e.g.
Bash(run_in_background: true)withwhile true; do sleep 60; done. - Quit the Claude Code process for that session (close the session / exit the app) without stopping the task.
- Observe the session list: the session shows as active indefinitely; its task list shows the task as running.
- Do not reopen the session — it never reconciles. (Reopening it triggers the orphan scan and marks the tasks stopped, confirming reconciliation exists but is resume-gated.)
Expected Behavior
Any of these would fix the UX:
- Reconcile orphaned tasks when the session process exits (the app knows the process is gone), or via a periodic scan across all sessions — not only on resume of the affected session.
- An optional max lifetime / TTL for background tasks, so a wedged task can't outlive its usefulness silently.
- At minimum: don't count never-reconciled background tasks toward the session's "active" status in the session list.
Environment
- macOS (Darwin 25.5.0), Claude Code desktop app 1.32352.0
- Model: claude-opus-5
- Related but distinct: #85037 (stale tasks in the Background Tasks panel after reinstall, chat unresponsive), #85181 (session process restarts orphaning tasks mid-session). This report is specifically about exit-time orphans being reconciled only on resume, which leaves closed sessions permanently "active".