Background-job bulk-reap logs cleanly-finished jobs as "(killed)" in daemon.log (v2.1.215) — corrupts exit telemetry

Status Open
Reported on v2.1.215
Maintainer reply None cached
Activity 0 comments · opened Jul 24, 2026

Bug report: background-job bulk-reap tags cleanly-finished jobs as "(killed)" in daemon.log

Version: 2.1.215 (GIT_SHA 316ce99628e89900bf0b1328fed3b8fec0c0c92d, Bun single-file bundle, Linux)

Summary: When the background-job daemon bulk-reaps settled jobs, jobs that already reached a clean
terminal state (state.json"state":"done") are logged as bg settled <id> (killed) in
daemon.log. Any tooling that consumes daemon.log for crash/exit telemetry then manufactures false
"forced kill" signals for sessions that actually exited cleanly.

Mechanics (from read-only inspection of the bundled code):

  • kill(e = SIGTERM, t = "killed", r) defaults the kill outcome to the literal "killed" unless the

caller passes t = "failed".

  • The bulk-reap call site iterates jobs and calls Q.kill(q) passing ONLY the signal — so the

outcome is always "killed", even when the job already recorded a clean terminal state.

  • stop(reason = "reap") settles with the unchecked kill outcome; only reason = "grace" force-sets

done.

  • The onSettle subscriber writes the bg settled <short> (<state>) daemon.log line with that state

verbatim. Notably the job dir IS in scope at that point (the code reads state.json a few
statements AFTER emitting the log line), and retireIfSettled() elsewhere in the same bundle
demonstrates the correct read-state-before-decide pattern — the reap path just does not use it.

Reproduction evidence (one concrete instance):

  • ~/.claude/jobs/<id>/state.json{"state":"done","firstTerminalAt":"2026-07-23T19:00:37.007Z"}
  • daemon.log at 2026-07-23T19:28:10.949Zbg settled <id> (killed)

(same job id; the "kill" is the routine bulk-reap ~28 minutes after the job finished cleanly.)

Expected: the reap path checks the job's recorded terminal state (as retireIfSettled() does)
and logs done for already-settled jobs, reserving killed for jobs that were actually terminated
while running.

Impact: fleet/ops telemetry that treats (killed) daemon.log entries as crash signals
over-reports session crash-exits. Workaround we apply meanwhile: treat a (killed) entry as a crash
only if jobs/<id>/state.json disagrees with a clean done.

View original on GitHub ↗