v0.10.0
Sessions stop lying about being stopped. csift status and csift wait now see every background shell, async agent and Monitor a session launched and whether it ever came back, wait requires a timeout because some of those tasks are designed never to return, and the lines csift used to skip become five new searchable leaves.
status and wait: background tasks
A Bash launched with run_in_background gets its tool result within milliseconds. The tail state machine paired it at once, so a session sitting idle with npm run dev still running read as a clean stop, and wait --until stop fired a false green. The harness itself writes nothing about a running shell at end of turn. Measured on 100 turn_duration records emitted while a shell was open, none carried a shell field. The REPL's "1 shell still running" lives in process memory only.
status now scans the whole main transcript for three kinds of launch: a backgrounded shell, an async agent, and a Monitor arm. It joins their completion notifications by the launching tool_use id, which is echoed exactly in every notification, across all three places a notification can land: a user record when the session was idle, or a queue-operation line plus a queued_command attachment when it arrived mid-turn. Launches are read from every lane, completions from the main file only, because that is the only place they ever land. Every open task prints as a bg row with its kind, id, launch instant and age, description or command, and the output file's size and last write. Closed ones fold to counts.
Not returned is not proof of running. Claude Code's own orphan summary, written at the next session start, says a task "may have been stopped (via the UI, Monitor timeout, or agent teardown, these leave no transcript marker)", and the section repeats that. Ctrl+C kills background agents only, never shells or monitors. The REPL hides no running task by age. The measured reality is that a long session carries several to dozens of dangling or days-old tasks: 24 of 3133 corpus launches never returned, and 22 of them were launched more than a day before their session ended.
status and wait: the seventh verdict and the lens
idle-background-open means the turn ended but background tasks the lens counts have not returned. It is neither running nor stopped, and it never satisfies --until stop. Two flags on both commands decide which open tasks count. --background-since WHEN counts only tasks launched at or after WHEN, using the shared time grammar, which now accepts 2mo and 1y, tolerates a leading minus, and understands now as the command's own start instant. --ignore-background RE excludes tasks whose command or description matches, and it repeats. Every task is still listed, with the rule that excluded it.
wait: a required timeout and a real report
This is the breaking change. --timeout is required, because a background task can be designed never to return, so an unbounded wait on stop was a correctness bug in every 0.9.x. A call without it is rejected with that reason. On every exit, fired or timed out, the report carries the tail state in words (in a Bash call for 34s, generating, idle), a census of what landed while waiting (tool calls by name, thinking, messages, prompts, notifications), the bg rows, and the last prompt and reply as excerpts.
Both commands print those excerpts under a warning written for a model reader. An excerpt is a partial view of the final state. It is useful for one thing, judging whether a background task is still meaningful, and it is never a review of the work. Counting tool calls and reading the last message is a check shallower than any human would accept, and a model holding a partial context tends to believe it read everything, even past an explicit tool error.
search: two classification fixes
The harness's agents-stopped notice ("2 background agents were stopped by the user: ..." and its singular form) was counted as a human turn. It is now harness.notification.subagent, never genuine, never a turn opener, rendered as [subagent stopped] .... A Background command pulse is now always background-command. The old quoted-name heuristic that routed re-arm and monitor-named commands to the monitor leaf predated the real Monitor tool and produced 40 false monitor records on one project against zero genuine pulses. Historical counts for those two leaves move by design.
search: the five promoted leaves
Claude Code writes several kinds of line that are not messages. Until now csift dropped them before parsing, and only show --line N --raw could reach one. This release promotes five of them into the label taxonomy, which grows from 28 to 33 leaves.
user.queued is a queue-operation line carrying the human's text. The label zone shows the queue event, so a hit reads user.queued [enqueue], user.queued [popAll] when the text was recalled into the input box, or user.queued [remove · absorbed_mid_turn] when it was consumed. The same facts ride JSON as queue_operation and queue_reason. harness.meta.turn-duration renders the turn_duration record as [turn duration: 1m 5s · durationMs=64911 messageCount=908 pendingBackgroundAgentCount=2], the structured body behind the REPL's "Done in 1m 5s" line. harness.meta.away-summary is the model-generated recap shown after five minutes away. harness.meta.stop-hooks is the Stop-hook execution ledger. harness.meta.snapshot covers the file-history snapshot and delta lines, so a question like "when did settings.json jump from v3 to v4" is a search by path.
All five are invisible to the model by the same instrument that made the compaction boundary invisible: none carries a message field. A promoted leaf is parsed only when an explicit -t reaches it or a show address names the line, so a bare scan never pays for them (measured 1.03x, noise). The three fabricated renders register their type value as a synth marker, so the whole-file gate stays sound. A zero-match run without a gated selector says so.
One correction to the 0.9.2 notes. The figure "about 61% of queued texts never become user records" counted every queue operation over three sessions. Counting enqueue lines only over six sessions, the human's prose reaches a user record between 72 and 81 percent of the time.
Verification
The full suite is green on all 8 release targets. Coverage held above the prior baseline, scoped mutation testing ran over every touched file, and every surviving mutant was either killed by a differential test or classified as a documented equivalent. The findings above come from three verification forks that measured the corpus and read the Claude Code binary rather than trusting the briefs. All binaries were built with path-privacy remapping and audited for path leaks.