Background Bash tasks falsely reported "was stopped" while still running; shell killed but process tree orphaned (Windows, idle sessions, machine-wide)
Summary
run_in_background Bash tasks are intermittently reported <status>killed</status> / Background command "…" was stopped with no user action — no TaskStop, no compaction, no session end, no OS-level kill.
Two things make this more than a cosmetic status bug:
- The sweep terminates the task's shell but not its descendant tree. A "killed" Maven suite kept running and printed
BUILD SUCCESS7 minutes after its kill notification. Its orphaned JVM still heldtarget/, so the immediate re-run failed with amvn cleanBUILD FAILURE that looks like a code/lock bug. Tasks whose workload is the shell (a puresleeploop) die instantly, so the symptom looks inconsistent from the outside. - A task that had already exited normally was still labeled
killedrather than completed-with-exit-code, so the status can't be trusted either way.
Net effect: killed is a sweeper verdict, not the command's outcome, and long verifies silently orphan process trees that collide with later runs.
Environment
- Claude Code 2.1.198 → 2.1.223 (observed across all of them; still present on 2.1.223)
- Windows 11 Home 26100/26200, shells are Git Bash (
C:\Program Files\Git\...\bash.exe) - Several concurrent CLI sessions on one machine (interactive +
--remote-control), transient daemon
Evidence
107 kill notifications across 58 session transcripts since 2026-07-05 on a single machine, in four different project directories.
Kills hit multiple independent sessions within milliseconds of each other — different OS processes, different projects:
| Instant (UTC) | Victims |
|---|---|
| 2026-07-12T12:40:28.722Z / .817Z | project A session + project B session |
| 2026-07-24T08:38:01.222Z / .237Z | project A session + project C session |
| 2026-08-01T22:53:53.575Z / .597Z / .605Z | three sessions in project A |
Kill notifications are phase-locked to a ~1 s tick (…:55.489, …:49.489, …:22.489, …:02.490, …:50.489 across 13 hours in one session), while genuine completion notifications arrive at arbitrary sub-second phases — i.e. these come from a periodic sweeper, not from child-exit events.
Only idle sessions are affected. Every kill landed 15 s – 8 min after the owning session's turn ended. Foreground Bash calls, the Monitor tool (4/4), and fully-detached Start-Process children were never affected.
Controlled repro (captured during investigation)
Launched 5 trivial background pollers (while …; do sleep 2; done, near-zero CPU/output; 2 of them appending a timestamped heartbeat line to a log every ~2 s), then let the session go idle.
- ~8 minutes after the last turn ended, all 5 were reported
killedin one burst. - The heartbeat logs stop dead at the same instant → these shells really were terminated (unlike suites with child trees, which survive orphaned).
- A different session that was mid-turn through the same window was untouched, as were long-lived background followers owned by the daemon's bg worker.
- Windows event logs (System + Application) are completely empty for that window: no sleep/wake, no Defender activity, no process crashes.
~/.claude/daemon.logshows no activity either (last entry ~3.5 h earlier). So this is not an OS/AV/power cause.
Kill-to-idle delay is inconsistent across incidents (15 s, 22 s, ~2 min, ~4 min, ~8 min), so it does not look like a single fixed idle timeout.
Suspected area
From strings in the shipped binary, background shells are tracked by PID + process start time, with an adopt/reap path (adopt.json, "exit handoff: N background shell(s) … handed to the next wake of this session", [adopt] reap, tengu_adopt_exit_reap, tengu_bg_orphan_reap → "reaped (roster gap)") and Windows liveness checks that already have acknowledged failure modes (tengu_bg_daemon_zombie_false_positive, tengu_bg_daemon_wmi_fallback). A liveness/ownership check failing transiently machine-wide would explain simultaneous kills in unrelated sessions.
Expected behavior
- A failed liveness/ownership check should be treated as unknown (retry with backoff), not as dead — never mark a task
killedwhile the parent still holds a live child handle. - If a background task genuinely must be terminated, kill the whole process tree (job object), so it doesn't leave orphans holding file locks.
- A task that exited on its own should report its exit code, not
killed. - The notification should include the reason for termination (idle sweep / liveness / user / session end) so it's distinguishable from a real stop.