Background Bash tasks falsely reported "was stopped" while still running; shell killed but process tree orphaned (Windows, idle sessions, machine-wide)

Status Open
Reported on v2.1.198
Maintainer reply None cached
Activity 0 comments · opened Aug 6, 2026

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:

  1. The sweep terminates the task's shell but not its descendant tree. A "killed" Maven suite kept running and printed BUILD SUCCESS 7 minutes after its kill notification. Its orphaned JVM still held target/, so the immediate re-run failed with a mvn clean BUILD FAILURE that looks like a code/lock bug. Tasks whose workload is the shell (a pure sleep loop) die instantly, so the symptom looks inconsistent from the outside.
  2. A task that had already exited normally was still labeled killed rather 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 killed in 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.log shows 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

  1. A failed liveness/ownership check should be treated as unknown (retry with backoff), not as dead — never mark a task killed while the parent still holds a live child handle.
  2. If a background task genuinely must be terminated, kill the whole process tree (job object), so it doesn't leave orphans holding file locks.
  3. A task that exited on its own should report its exit code, not killed.
  4. The notification should include the reason for termination (idle sweep / liveness / user / session end) so it's distinguishable from a real stop.

View original on GitHub ↗