Skip to content

Releases: wdhwg001/csift

v0.11.0

Choose a tag to compare

@wdhwg001 wdhwg001 released this 06 Sep 08:07

The csift channel: a way to get a message to a Claude Code lane the official channel cannot reach, and to find out afterwards whether it arrived.

Why a channel

Claude Code has its own messaging. A session sends to a teammate by name, to one of its own subagents by id, or to another session over a local socket. csift can now name the right official call for a pair of lanes, and for most pairs that is the whole answer. The channel exists for the pairs where the official transport is not weaker but absent. A running workflow lane cannot be reached by the send tool at all. An unnamed subagent has no way to address the subagent that spawned it, because main reaches the top-level conversation and not the parent. Nothing wakes an idle session that never published a socket. And a sender outside Claude Code, a human at a shell or a Claude in another harness, holds no tool to call in the first place. csift reads and writes the same on-disk shapes those senders can see, so it can carry a message where the tool cannot.

The contract changed, so this is a minor release

Through 0.10.5 csift wrote nothing. From 0.11.0 exactly three commands write, and they write exactly one place. send, deliver and ack append to a csift-channel directory that csift creates inside the session's own sidecar folder, beside the subagent transcripts. Never a transcript, never the team mailbox, never the messaging socket, never the session registry, never a settings file. csift does not install its hook either: csift deliver --recipe prints the settings block and a person pastes it. Every other subcommand still only reads. Nothing that worked before stops working. The promise a reader had was narrower than the one they have now, and a narrower promise is a breaking one.

What is new

csift send @<lane> "message" queues one message for one lane and prints one verdict. OK means the receiver is alive, its delivery hooks are configured and armed, and the message fits. FULL means it is queued but needs more slots than the receiver has. MAY-FAIL names a risk. UNPREDICTABLE means the receiver is not alive and nobody can say when it will be. REFUSED means it can never arrive and nothing was queued. A refusal exits 0, because it is a definitive answer about the receiver and not a usage error. Where an official transport does exist, the receipt prints the exact call for you to make, and csift queues its own copy anyway.

csift deliver --slot k is the hook entry. You never run it by hand. Each installed line carries its slot number, the slots of one event order themselves through a small marker chain, and slot k emits chunk k of whatever is waiting. A message that asks to arrive at a turn boundary may hold the turn open through the harness's own stop-hook mechanism, while the harness's block cap allows it, and never chains two holds for one message.

csift msg <id> answers the question a queue cannot: did it actually arrive. The per-lane ledger records what csift emitted. The receiver's transcript proves what landed, as a hook-context attachment carrying the message id. msg joins the two into one verdict, and the proof half on its own is exactly csift search '<id>' @<lane> --additional-context, so the join can be audited without trusting it. csift ack <id> records the one thing only a receiver can say.

whoami gained a lane layer. It accepts an agent id in either of a teammate's two forms, prints self, parent and topology sections, and answers --to @<lane> with a reach prediction that sends nothing. --peers lists every live lane as id, kind and state and nothing else. A description or a role-shaped name is the material one lane would use to claim standing over another, so the census says who is alive and not who should be obeyed. Outside Claude Code, whoami prints the not-a-lane answer with the one channel out and what a receiver needs installed, and exits non-zero, because there is no session to identify.

A delivery lands in the receiving lane as a hook-context attachment, and agent.communication.channel is the one attachment label a default search reaches, because a message addressed at the lane is not machinery. It renders verbatim, envelope header included, so a reader sees who sent it, under what relation, and that it came from neither the user nor the harness. Every other hook attachment stays behind its flag, and two new tests pin that a document which merely quotes the envelope header never leaks through.

A teammate carries two ids minted apart at spawn. The routing form Name@Team is what the official send tool needs, and it can collide when two teammates share a name. The transcript form on disk never collides. csift resolves either, keys all of its own work on the transcript form, teaches which one the official tool wants, and prints both wherever a teammate appears.

To answer "will a delivery hook actually run there", csift now reads Claude Code's settings cascade the way Claude Code does: five scopes in the harness's own order, env merged per key, hooks concatenated per event, plugin hook manifests unioned, the policy tier composed first-wins, and the three policy switches that can empty the whole hook set. Every receipt and every reach prediction names the scopes it read and the inputs it cannot see (a settings file passed on the command line, a restricted source list, the trust dialog's state, a managed profile), so a gate verdict says "unknown" with its evidence instead of implying the file scopes are the whole story. plan reads its plansDirectory through the same model with its precedence unchanged.

What the build found and fixed on the way

The channel was built by a fleet of implementers, each paired with a verifier that re-ran the whole gate and had to catch a planted fabrication in the implementer's report before its acceptance counted. Those verifiers found real defects before any of this reached a user. The default-on candidate needle for the channel label let an ordinary hook context that merely mentioned the envelope literal into a flagless search. A post-parse gate now keeps both gated attachment leaves behind their flags. The policy table treated a subagent addressing its own parent as a sibling and would have handed the message to a transport with no parent arm. The relation is now read from csift's own topology in both directions, and a real parent never receives the peer caution sentence. A regex whose construction carried an expect in a library path was replaced by a token walk. The reach prediction borrowed a sentence from the send receipt and claimed a write it never made. The hand-written command list in the root help had drifted from the command enum in two places, and a drift guard now pins the two together.

Verification

The suite is 1243 unit tests and 690 end-to-end cases, all passing on every one of the twenty-one commits behind this release, because each one went through the pre-commit gate: the structure limits, formatting, clippy with warnings as errors, the full suite, and the introspection ledger gate. Line coverage is 96.14 percent (983 lines missed of 25,472), above the 95.79 percent no-drop baseline of 0.10.5 after a top-up pass that added twenty-six tests and changed no production code.

A scoped mutation census ran over every source file this release touched, 55 files and 1,476 mutants, at two jobs with a disk watchdog. It reached 1,242 caught before the coverage top-up's tests were counted, 1,261 after a re-test of the survivors, and 1,314 after a kill tranche wrote fifty-three differentiating tests, each verified against its own mutant by a targeted census. Of the 51 that survive, 14 sit in this release's code and are equivalent by construction (an early-out whose absence changes no output, a hash mixer whose only contract is sixteen fresh hex characters, an arm compiled only off this platform) and 37 are the documented pre-existing zones of earlier releases, each classified with its reason in the commit that closed the gate. The score is 96.3 percent counting timeouts as kills, and 96.1 percent counting caught mutants alone, against 91.2 percent for the previous release. Every gate ran on this machine, so the timeouts are the conventional loop-counter mutants, not load.

The introspection ledger holds 616 claims, 94 of them in the new messaging area, one per Claude Code behaviour the channel depends on: every hook event's payload, the additionalContext persistence threshold, the stop-hook block cap, the settings cascade and its two feature gates, the resume state machine, the official mailbox and socket shapes, and the history floors of each literal across sixty cached builds. The README table states how much of that is traced end to end and how much rests on a producer trace or a specimen alone. The messaging claims land mostly below the end-to-end line, with byte offsets but not yet the verbatim three-hop excerpts the next audit will add.

The build itself ran as three layers of Opus implementers in isolated worktrees, each paired with a read-only verifier that re-ran the whole gate and had to catch a planted fabrication in the implementer's report before its acceptance counted. Every verifier caught its canary. The whole pipeline, send to deliver to msg to search, was then driven by hand against a throwaway configuration home before the docs were written.

The eight binaries were built from the tag on their own platforms, and the full suite ran on every platform that can run it. The four Linux lanes (gnu and musl, arm64 and amd64, the amd64 pair under emulation) each passed 1243 unit tests and 690 end-to-end cases. The two Windows lanes, arm64 native and x64 under emulation on the same guest, each passed 1242 and 689, the two absent tests being the unix-only process probe and its locale pin. The two macOS binaries were built with path remapping on the development machine, where...

Read more

v0.10.5

Choose a tag to compare

@wdhwg001 wdhwg001 released this 04 Sep 12:01

A correction release. A reviewer pointed out that the ledger shipped with drift verdicts nobody acted on, and that its verifiers had learned to stop at an honest report. Both were true. This release fixes the four csift defects those verdicts named, corrects four statements csift printed or taught, and changes the ledger's gate so that neither can ship again.

What was wrong in csift

image joined the #N handle to the image blocks by position. A record's imagePasteIds lists the ids in the order of its image blocks, while the [Image #N] markers keep the order the operator typed them in. The two orders differ on 18 of 662 records in the reference corpus, and each of those got a silently wrong handle. The join is now by number, with the positional zip kept only for records that carry no id array.

recover replayed a Read echo that carries no text. Claude Code blanks the text of a tool result older than its retention window before persisting it, leaving the line counts intact. The reference corpus holds 320 such echoes, and four result arms never carry a text at all. A blanked echo of a complete read used to pass the whole-file test and replay as a snapshot of nothing, replacing known content. Such an echo is now counted as a blanked read with a soft annotation boundary, and nothing is replayed from it.

The status and wait pid probe ran ps with whatever locale the caller had. A German or French locale renders the start time with names no English pattern parses, and the pid-reuse guard was silently skipped on those hosts. The probe now pins LC_ALL=C and TZ=UTC on its own call, exactly as Claude Code pins its own acquisition of the same value.

Every search hit now carries refetch_uuid beside refetch. A line number is a durable address only while the transcript is append-only, and three Claude Code paths rewrite a live transcript in place. The uuid survives them.

What csift said that was not true

TaskStop resolves a teammate by its name or by name@team from Claude Code 2.1.198. The agents footer, the JSON control hint, the SKILL rows and the hook recipe all said it rejects every form, which was true on the build of the original incident and false since. A live trial at 2.1.258 confirmed the correction: the bare name stopped the teammate, the name@team form resolved it, and the aName-<hex> transcript id csift prints was the only form that missed.

The file-history checkpoint store is written by the structured file tools and by an approved in-place sed preview. The recover --list-backups banner, its help and its module doc said bash edits never land there. A live trial in default permission mode showed the preview dialog rendering the sed as a file edit, the checkpoint taken at approval, and a tool result indistinguishable from a real run.

Whether a pending AskUserQuestion reaches the transcript while its dialog is open is a timing outcome of Claude Code's write frontier, not a rule keyed on the question count, and not something that changed at 2.1.258. A comment, a user-facing note, the help and three documents said otherwise. The two measurements stand as measurements.

The registry row writer stamps procStart on every platform. The two-key procStartFt schema belongs to the .key companion file. A comment in csift's registry reader had attributed it to the row. A Windows trial read the companion: it holds a peer token, the start time as a tick count and the pid domain, and a print-mode session writes only the companion, no row.

The ledger

The gate gained three rules. A claim's latest check can no longer be drifted: the fix or the retirement has to append its own check after the drift, so a ledger whose last word on a claim is a drift cannot be released. No open leg may be an unconsumed text correction or a record of a rejected rewrite, because both mean a refuted text is still shipping. And every claim below end-to-end has to carry both attribution legs, so the derivation always runs.

The attribution set gained upstream: the producer lies outside the shipped binary by construction, on the model or API side, in the operating system, or in a native runtime binding, while the client-side treatment is traced and a specimen observed. A search that failed is never upstream.

The round that produced this release traced or reframed every claim whose producer was incomplete. The table in the README is regenerated from the ledger and checked by the gate. Where it stood before and where it stands now:

attribution before now
end-to-end 382 470
producer-only 33 33
upstream 0 14
specimen-only 105 5
partial-producer 2 0
by-elimination 0 0

Every one of the 470 carries its three hops as verbatim excerpts at byte offsets, and every one was re-read by an adversarial verifier that had to catch a planted fake first. Forty-two fakes were planted across the round and forty-two were caught.

The ledger's text now follows a prose law: no calendar dates, no csift version numbers, no narrative about the audit that produced a sentence. The commit and the release carry those. The README's verification section states what this project will never publish and why the ledger is re-verified periodically rather than for every Claude Code release.

Verification

The suite runs 1046 unit tests and 552 end-to-end tests on every commit through the pre-commit gate, which also runs the ledger gate by name. Line coverage measured 95.79 percent (889 of 21129 lines missed), holding the 0.10.4 baseline. A scoped mutation run over the eight changed files tested 363 mutants: 326 caught, 1 killed by timeout, 14 unviable and 22 survived. Five of the survivors were real gaps in code this release touched, in the image walk and in the read-event arithmetic. Two new unit tests kill them, and a targeted re-run confirms all 22 viable mutants in those two functions are now caught. The remaining seventeen sit in zones classified in earlier rounds: the prefilter widening and the base64 decoder, the buffer-disagreement threshold, the background-append arm, and the Windows and fallback bodies of the pid probe, which this host does not compile and which were exercised live on a Windows guest instead.

The ledger's own checks were written claim by claim. Every claim below end-to-end names the instrument that would close it, and the live trials this round ran on this machine closed seventeen such legs: a permission dialog held open while the registry row and the transcript were read, a NotebookEdit call, an escape recall that was never resent, a subagent that backgrounded a shell which completed while it still ran, TaskStop against a live teammate in three id forms, a scheduled task firing, an in-place sed approved through its preview, csift's own probe inside a BusyBox container, and on a Windows guest a session started under a divergent HOME, the companion key file of a running session, and the process queries by hand for a live and a missing pid.

Eight targets were built at the tag and ran the full suite on their own platform.

The four Linux lanes (glibc and musl, x86_64 and arm64) ran in the official rust:1.89 containers, 1046 unit and 552 end-to-end tests each. The two Windows lanes (arm64 native, x64 emulated) ran on a Windows 11 ARM64 guest, 1045 and 551, the missing two being the unix-only pid probe and its locale pin. The two macOS binaries were built with path remapping and smoke-tested. Every artifact was audited for machine-path strings and the Windows executables for a dynamic C runtime, all clean. The packaged documentation is byte-equal to the tag.

v0.10.4

Choose a tag to compare

@wdhwg001 wdhwg001 released this 04 Sep 05:04

A patch release for one reader defect the 0.10.3 ledger work exposed but did not fix.

A live transcript must never be memory-mapped by a poll loop

A memory map of a file that another process truncates faults with SIGBUS on the first page touched past the new end, and no error value catches it. The ledger's trace of Claude Code's transcript writer (claim MISC-020) shows two paths that rewrite a transcript in place: a rewind tombstone truncates the file and rewrites its tail, present since at least 2.1.191, and the local garbage collection rewrites the file on compaction when it is armed. wait mapped the whole transcript on every poll to read the few bytes appended since the last one, and the tail window that status and wait classify was mapped the same way. A rewrite landing inside one of those windows would have killed the process without a message. The hazard was measured before the fix: a one megabyte file mapped read-only, truncated to four kilobytes by a second writer, read once past the new end, and the reading process dies with signal 10 on macOS (signal 7 on Linux).

Both repeated readers now use plain positional reads. A shrink returns fewer bytes and nothing faults. wait also detects a transcript that is shorter than its baseline between two polls, moves the baseline to the new end and reports it in the activity line (transcript shrank N time(s) ... rewritten in place, baseline moved) and in JSON (activity.shrinks). Before, a shrunk file was skipped in silence. The one-shot full scans (search, stats, the background scan of status) keep the memory map the performance contract rests on, and the specification now states the hazard as it is: a sub-second map bounds the exposure, a polling map does not.

Verification

The suite runs 1042 unit tests and 550 end-to-end tests on every commit through the pre-commit gate, which also runs the ledger gate by name. Two new end-to-end tests drive wait against a transcript that is truncated below its baseline and then appended to (the pulse fires and the shrink is disclosed with its byte count) and against two appends landing in separate polls (both read once, no false shrink). A unit test pins the positional readers on a file another writer shrinks. Line coverage measured 95.79 percent (888 of 21092 lines missed), above the 0.10.3 baseline. A scoped mutation run over the four files touched tested 149 mutants: 116 caught, 18 killed by timeout, 3 unviable, 12 survived. Three of the survivors were real gaps in the new poll code (the shrink byte count and the cursor advance) and were killed by the tests above (a targeted re-run confirms). The other nine sit in previously classified equivalent zones (window alignment on a leading empty line, the adaptive poll cadence, a constant and a capacity hint). Ledger claim MISC-020 carries the SIGBUS probe as its instrument and records the fix.

Eight targets were built at the tag and ran the full suite on their own platform. The four Linux lanes (glibc and musl, x86_64 and arm64) ran in the official rust:1.89 containers, 1042 unit and 550 end-to-end tests each. The two Windows lanes (arm64 native, x64 emulated) ran on a Windows 11 ARM64 guest, 1042 and 549, the one missing test being the unix-only pid probe. The two macOS binaries were built with path remapping and smoke-tested. Every artifact was audited for machine-path strings and the Windows executables for a dynamic C runtime, all clean. The packaged documentation is byte-equal to the tag.

v0.10.3

Choose a tag to compare

@wdhwg001 wdhwg001 released this 04 Sep 03:58

A patch release built around one question: for each Claude Code behavior csift depends on, how completely has that behavior been attributed? The introspection ledger now answers it claim by claim, with the producing code traced in the shipped binary and re-read by an independent verifier, the README carries the tally as a table the gate regenerates, and the five defects the tracing found in csift are fixed.

What the tracing found in csift

The freeform AskUserQuestion answer. Claude Code 2.1.258 synthesizes the result of an AskUserQuestion from five branches. When the answerer writes the freeform response field instead of choosing an option, the record carries an empty answers map and the text The user responded: <text>. csift required a non-empty answers map and knew no such prefix, so that record classified as a plain tool result and opened no turn. A present, non-blank response is now a second structured signal beside answers. The AUQ unit renders the questions asked and a response: line. The TUI dialog never writes response (a typed "Other" lands in answers, and "Chat about this" is a rejection). The answerer that writes it is recorded in the ledger as an open leg, not as a guess.

A blocked background task. Claude Code's remote-agent notifier writes a fifth terminal status, blocked. csift mapped every status outside failed, killed and stopped to completed, so a blocked agent, or any value a future build adds, was booked as a clean completion. status and wait now count blocked and N with an unknown status as their own buckets, in text and in JSON.

A notification in a child lane. wait --until notification watched the main transcript only, on the belief that a pulse never lands in a child lane. csift's own classifier counts 2906 delivered pulse records across all lanes against 2904 in top-level transcripts: a pulse addressed to the owning agent lands in that agent's lane. The condition now fires in every watched lane.

A read cut by its token budget. The Read tool marks a read it cut at its token budget with truncatedByTokenCap. On a file whose lines are too long to paginate the harness recounts numLines from the cut slice, so it can equal totalLines, and csift's full-read test replayed truncated content as the whole file. A flagged read is never a full snapshot now. Its whole lines still anchor as a partial read.

An image in a queued prompt. A prompt queued and then edited or recalled before dispatch never becomes a user record. Its pasted images survive only in the queued_command attachment. csift image read user records and tool results only, so 45 of 52 such images in the reference corpus were invisible. The attachment is the third carrier the walker reads.

Attribution in the ledger

Every claim carries two legs. The producer leg records whether the producing code was traced in the shipped binary. A complete trace is three hops quoted verbatim at byte offsets: the trigger, the gate that selects the fact, and the writer or template, each excerpt re-read within 400 bytes of its offset by an independent verifier whose batch carried a planted fake. A partial trace found a template or field without its trigger and gate, and none means nothing was traced. The specimen leg records whether the behavior was observed on disk or in a live trial. The attribution follows from the pair. A claim that is not end-to-end lists the instrument that would close each open leg, and an end-to-end claim carries none.

Three rules kept the round honest. A negative such as "nothing else writes this" closes only by an enumeration of every site in the binary, each read and listed. A claim whose text a traced hop refuted was rewritten by a scribe and re-read against the chain, never annotated around. Version floors were checked against historical native builds fetched by version, bisected on string literals: the file-history delta line dates to 2.1.208, the five-way AskUserQuestion synthesis to 2.1.216, the isMeta-less compaction boundary to the engine route that shipped in 2.1.193.

The tally at this release (the same table the README carries):

attribution claims share meaning
end-to-end 382 73.2% the writer, its gate and its trigger read in the shipped binary, and a specimen observed on disk or live
of which chain traced and adversarially re-read 382 73.2% the three hops quoted at byte offsets in producer_chain, every excerpt re-read by an independent verifier
of which audit-graded, chain not yet traced 0 0.0% graded end-to-end from the release-audit checks (writer offsets cited there) before the three-hop tracing existed, the next audit traces them
specimen-only 105 20.1% observed on disk or live, the writer not traced (or traced only in part) (92 of them with a partly traced writer)
producer-only 33 6.3% the writer traced in full, no specimen exists in the corpus or could be produced here
partial-producer 2 0.4% a template or field located without its gate and trigger, no specimen
by-elimination 0 0.0% neither leg, attributed by exclusion or from csift's own design
total 522 100.0% one claim per Claude Code behavior csift depends on, verified at Claude Code 2.1.258

Every end-to-end claim carries its chain: the three hops at byte offsets in producer_chain, a negative's enumeration of every site, and a residue list for the notes that are not gaps. A claim below end-to-end names, in open_legs, the instrument that would close it: fourteen of them cannot be closed on this host at all (a Windows result, a remote agent reaching blocked, an MCP elicitation, a held permission prompt, an armed local garbage collection) and say so.

The gate derives the attribution from the legs and refuses a disagreement, refuses a holds verdict on a by-elimination claim, refuses an end-to-end claim with an open leg, and requires the README table to equal the ledger's counts, so the table is regenerated, never typed.

Verification

The test suite runs 1040 unit tests and 548 end-to-end tests on every commit through the pre-commit gate, which also runs the ledger gate by name. Line coverage measured 95.78 percent (888 of 21048 lines missed), above the 0.10.2 baseline of 95.70. A scoped mutation run over the six files the fixes touched tested 306 mutants: 261 caught, 24 killed by timeout, 12 unviable, 9 survived, and every one of the 9 sits in a previously classified equivalent zone (the base64 decoder's bit-or, the marker parser's guard, the condition's perf widening, the render's mtime and lane co-variance, the adaptive poll cadence). Every mutant in the new code was killed. The crate-wide mutation badge stays at the 0.10.1 census.

The ledger gate holds eight rules on the 522 claims, and the release audit for this version was the tracing itself: three rounds of tracer and verifier fleets, each verify batch qualified by a planted fake that every verifier caught, followed by three scribe passes for the refuted texts (174 rewrites accepted, 55 rejected, each rejection leaving its claim below end-to-end). Two live trials on Claude Code 2.1.259 back two claims: a persistent Monitor ran 150 minutes without a completion record, and a /rewind restore left the transcript byte-identical, the resubmitted draft appending after it.

Eight targets were built at the tag and ran the full suite on their own platform. The four Linux lanes (glibc and musl, x86_64 and arm64) ran in the official rust:1.89 containers, 1040 unit and 548 end-to-end tests each. The two Windows lanes (arm64 native, x64 emulated) ran on a Windows 11 ARM64 guest, 1040 and 547, the one missing test being the unix-only pid probe. The two macOS binaries were built with path remapping and smoke-tested. Every artifact was audited for machine-path strings and the Windows executables for a dynamic C runtime, all clean. The packaged documentation is byte-equal to the tag.

v0.10.2

Choose a tag to compare

@wdhwg001 wdhwg001 released this 03 Sep 05:41

A patch release from the introspection ledger's first re-read after 0.10.1. Seventeen claims had no code site. Anchoring them, and re-reading the claims the previous audit had marked drifted, turned up the defects below. Every fix carries its ledger claim with the instrument that measured it, and the gate now refuses a claim with no code site.

wait: notifications absorbed mid-turn

wait --until notification fired only on the user record Claude Code writes when a completion arrives while the session is idle. A completion that lands mid-turn never becomes a user record: it exists on a queue-operation enqueue line and on a queued_command attachment. In this corpus the main transcripts hold 3218 pulse-bearing user records against 5893 enqueue lines, so roughly every other completion was invisible to the condition. The condition and the activity census now read the same three carriers the background section already joined. A queue remove or dequeue repeats the enqueue's pulse and counts nothing.

plan: the project root

The slug-only binding resolved plansDirectory against the slug record's own cwd. That field follows the tracked shell cwd, so a cd into a subdirectory before the first slug record joined the plan file under the subdirectory, and a plansDirectory set in the project's own .claude/settings.json was silently dropped because the settings lookup was anchored on the same drifted path. Both were reproduced on 0.10.1 against a synthetic tree. Claude Code memoizes its plans directory at the first access early in the session, through the same cwd getter that stamps records, so csift now binds against the transcript's first recorded cwd and reads the settings scopes from that root. A session resumed from another directory re-memoizes in the harness, which this instrument cannot see.

search and agents: fork clones

A /fork child's transcript is a clone of its parent's, opening with a fork-context-ref record. Its first turn-opener is therefore the parent's own human message, which 0.10.1 labeled as the spawn-prompt seed (agent.communication.inbox). Ten of the 42 clones in this corpus carry such a record. A clone now has no seed. The global spawn index also folded subagent locals later-wins, so a clone's copy of a sibling's spawn record could re-parent that sibling onto the clone. The fold is first-wins now, the main transcript first and the subs in discovery order. A superseded draft whose text is sectioned kept fanning out into per-section classes its own labels[] did not carry. It keeps the single user.unsent view.

strings and help

Two shipped strings had lost their line-continuation backslashes and printed runs of spaces: the wait timeout-guard message and a recover bash-append boundary detail. show --turn help now says which gated lines a turn fetch omits and how to address them. search --help names the eight LLM-invisible leaves instead of "exactly two".

The ledger

AGENTS.md section 7.1 documents the ledger schema field by field, the verdict semantics, the six gate rules and the four procedures (a new behavior, moved code, a release audit, a retirement). Rule 6 is new: every claim cites at least one code site.

Verification

The suite at the release commit is 1037 unit tests and 544 end-to-end tests (543 on the two Windows targets, where one unix-only process probe is compiled out), green on all eight targets of the release matrix. Line coverage is 95.69 percent, level with 0.10.1.

The nine files the fixes touched went through a scoped mutation pass: 378 mutants, 328 caught, 20 unviable, 3 timeouts and 27 survivors. Every survivor but one sits in the classes earlier releases documented (prefilter and performance-gate arithmetic, window math, the spawn-lookup stubs, the tool-result dual chain). The one survivor in new code, the last disjunct of the notification match, was killed by a regex that only the raw carrier text satisfies, and a targeted re-run caught all ten mutants of that function.

The ledger holds 521 claims, every one anchored to at least one verbatim code site, which the gate now requires. The eight claims behind this release's fixes carry a 0.10.2 check with the instrument that measured them: the corpus line census behind the notification carriers, the synthetic-tree runs behind the plans directory root, the fork-clone and fork-meta censuses, and the string and help audits.

The measurements a reader may want to repeat: in the 67 main transcripts of the reference corpus, 3218 pulse-bearing user records against 5893 queue enqueue lines. There are 42 subagent transcripts opening with a fork-context-ref record, 10 of them carrying a genuine user record, and 33 fork metas, none carrying parentAgentId.

v0.10.1

Choose a tag to compare

@wdhwg001 wdhwg001 released this 03 Sep 00:23

The 0.10.0 release matrix ran the Windows test suite green, but the suite never exercised process liveness there, because the pid probe was compiled out on Windows. This release closes that gap the only honest way, by running a real Claude Code 2.1.258 session on a Windows 11 ARM64 machine and pointing csift at it: a Sonnet 5 session that built a Next.js todo app through four subagents and left the dev server running as a background task. Everything below was measured on that session or on the Claude Code binary.

status and wait: the registry's shell status was misread

The harness's session registry has a closed status set: busy, shell, idle and waiting. csift 0.9.0 through 0.10.0 treated shell as a running shape. The binary tells a different story. It computes idle and then relabels it shell while a background shell task is open, so shell means exactly the seventh verdict's situation, an ended turn with a dev server still up. On the Windows session that mistake produced running where idle-background-open was true, and wait --until stop could not fire even when the lens ignored the dev server. busy is now the only registry running signal, shell reads as the idle-with-background-shell shape it is, and the note says whether the background section lists the shell or the lens excluded it.

status and wait: Windows gets a real pid probe

A Windows registry row renders procStart as a FILETIME tick count rather than the asctime string unix writes, so the old parser could not read it and would have skipped the reuse guard silently even if a probe had existed. The probe now reads the owner's start time through PowerShell's Get-Process, falls back to tasklist for liveness alone, and compares instants with the same two-second tolerance as unix. A row whose pidDomain names another domain is never probed, and the verdict says so. Verified against the live session with the start-time guard matched, and after a forced kill of the Claude Code process with a stale-dead verdict on both platforms.

waiting on a human: two more instruments

The registry's waiting status is set by the harness for any blocking dialog, which includes permission prompts, plan approvals and sandbox or worker requests. csift now reads it as waiting-hitl, and the idle-verdict honesty note names the status the row carried instead of calling every permission prompt invisible. The second instrument came from a measurement that overturned an old law: Claude Code 2.1.258 writes a multi-question AskUserQuestion to the transcript at question time, while a single-question one still stays buffered until it is answered. Three single-question trials and two multi-question trials, on macOS and Windows, agreed. An unreturned AskUserQuestion or ExitPlanMode at the main tail is therefore waiting-hitl without the sidecar. The sidecar remains the only instrument for the single-question shape.

child lanes settle when the harness says so

A subagent that finished seconds ago still carries a fresh tail without an end_turn, so the recency rule counted it as live for up to five minutes. A lane whose completion notification already landed in the main transcript is now settled regardless of its tail.

the introspection ledger

csift depends on hundreds of small facts about what Claude Code writes, and those facts drift. This release adds INTROSPECTION.json, a ledger with one entry per claim: the behavior, what csift does with it, the code site with a verbatim snippet, how to re-verify it from scratch, the version it was first pinned against, and a check record per release audit carrying the instrument that ran, what it observed and the counting rule. The ledger was excavated from the specification, the operating manual, the code comments and csift's own development sessions, then every claim was re-verified against the current binary and corpus by a separate agent. A pre-commit gate ties the README's "verified against Claude Code" badge to it. The badge version is admitted only when every claim carries a check at that version, check evidence must be claim-specific, and every cited snippet must still exist in its file, so a refactor that moves code fails the gate instead of orphaning the claim.

The first audit already paid for itself. A verifier reading the binary's settings resolver found that plansDirectory is resolved against the project root through the merged settings scopes, with a containment check that refuses an escaping value, while csift's slug-only plan binding joined a relative value to the config home. csift plan now follows the harness rule, with a test for the project join, the project-local override and the escaping fallback.

A second verifier explained a number that had puzzled an earlier round: forked lanes reporting depth 65. A /fork child's transcript is a clone of its parent's, so it carries the spawning tool call itself, and csift's spawn join made every forked lane its own parent until the depth walk hit its cycle cap. csift now reads the parentAgentId the harness writes into the child's meta file and never accepts a node as its own parent. Two smaller corrections came from the same pass: the parent's record of a subagent return carries an appended continuation footer rather than a truncation, and recover's Bash read anchors already reach built-in and teammate lanes, since only workflow lanes lack the result echo.

The audit's largest correctness catch was silent. Claude Code relays a peer message under three preambles, and csift recognised only the first, so an inbound message relayed while the session was mid-turn carried no label at all: 29 of the 47 peer messages in the reference corpus were invisible to every census and selector. All three preambles are boundaries now, and the corpus-wide inbox count moves from 8721 to 8750. Two smaller ones ride along: the third phrasing Claude Code uses to write an AskUserQuestion answer is recognised as an answer, and the registry reader accepts the procStartFt key the harness now carries beside procStart.

search: the 34th label

The harness writes a handful of type:"system" records for its own screen that never reach the model, and csift had no name for them. One appeared during this release when an account switch in another session disconnected Remote Control: a system record with subtype informational, level warning, and the notice as its content. harness.meta.system now covers that whole family, including api_error, the model-refusal fallbacks, agents_killed, local_command and scheduled_task_fire, and whatever a later build adds, rendered as [<subtype> <level>] <content>. It is gated like the other promoted leaves, so a bare scan never pays for it, and show --line renders it without --raw. The claim behind it names the record shape and the counting rule in the ledger.

README

Highlight 8 is laid out as short lines. Two badges join the header: the Claude Code version the ledger was verified against, and the crate-wide mutation score from a fresh census.

Verification

The suite at the release commit is 1036 unit tests and 540 end-to-end tests, green on all eight targets of the release matrix (539 on the two Windows targets, where one unix-only process probe is compiled out). Line coverage is 95.68 percent, up from 95.58 at 0.10.0.

The mutation badge comes from a fresh census of the whole crate with cargo-mutants: 5858 mutants, of which 215 could not be built. Of the 5643 viable ones the suite caught 4647 outright, 498 hung the suite and were killed by the 30 second timeout, and 498 survived. The badge counts a hang as a kill, which gives 91.2 percent. The caught-only floor is 82.4 percent and the timeout share is 8.8 percent. The census ran under a heavy machine load, so every timed-out and surviving mutant was queued for a second pass at a 45 second timeout on a quiet machine. The first 131 of that pass, 47 timeouts and 84 survivors, reproduced the census verdict in every case (the timeouts are loop-counter mutants such as i *= 1), and the pass was stopped there as evidence enough. The survivors in the code this release touched were read one by one. Four new pins landed: a literal per-platform pin for the pid domain, whose old test had asserted the function against itself, the returned-set rule through the whole status join, the shell note's absence on a busy row, and a two-agent end-to-end run where one completion pulse has landed and the other has not. Reading them also removed a duplicated background scan that the status join ran twice. The rest are the documented equivalent classes: Windows-only probe arms that do not compile on the census host, arithmetic in loop guards that only widens a prefilter, and comparisons on values the domain never produces.

The introspection ledger holds 521 claims. Every claim carries a check at Claude Code 2.1.258 with the instrument that ran, what it observed and the counting rule. Of those, 153 held as written, 344 were refined, 19 had drifted (all fixed in this release or recorded as ledger corrections), and 5 could not be verified on this host. The pre-commit gate passed on every commit of the release.

The Windows verification is the session described at the top: Claude Code 2.1.258 on Windows 11 ARM64, Sonnet 5, four subagents, a dev server open as a background task. Against it, status reported idle-background-open with the process start-time guard matched, --ignore-background npm turned that into idle-eot, wait --until stop fired in under a second under that lens and timed out with exit code 124 without it, and a forced kill of the Claude Code process produced stale-dead. The same kill on macOS produced stale-dead under SIGKILL and an honest idle-eot with no registry row under SIGHUP, where Claude Code deletes its own row.

v0.10.0

Choose a tag to compare

@wdhwg001 wdhwg001 released this 02 Sep 03:45

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.

v0.9.4

Choose a tag to compare

@wdhwg001 wdhwg001 released this 01 Sep 23:46

Bash reads become reads and bash writes become writes in recover. Plan binding now matches Claude Code's own rule, list names a forked clone's origin, status shows what is actually moving, and regex searches with metacharacters stop paying full price.

recover: bash content anchors

A session steered toward shell-based file editing used to starve recover of anchors. Every bash write was a content-less boundary, and a file written only through the shell had no recoverable history at all. The deterministic subset of shell commands now replays as first-class content. Every admission gate errs toward refusing, so a wrong anchor never enters the replay.

Writes anchor per segment, which admits the dominant real shape: write the file with a heredoc, then run it, in one compound command. The admissible forms are a quoted-delimiter heredoc through cat or tee (the body sits byte-verbatim in the transcript), a literal echo or printf, and truncate -s 0. An unquoted delimiter is accepted only when the body is free of expansions. A compound command must also produce a clean result echo, meaning empty stderr and no interruption. Only the last segment owns the exit code, and a failing write always complains on stderr, so a clean echo is real evidence. If any other part of the command touches the same resolved path, the anchor is refused.

Reads anchor only as single simple commands (cat, head -n N, sed -n 'A,Bp') under a completeness gate. A window that starts at line 1 and hits EOF counts as the whole file. A byte-known >> append is placed only onto a buffer that is already complete and newline-terminated. Anywhere else it is disclosed as a bash_append_unplaced boundary.

Some forms stay out on purpose. tail cannot be placed in a line-keyed buffer, sed -i yielded zero literal cases in measurement, and variable targets, interpreter heredocs, ssh heredocs, and here-strings are all refused. Coverage reports count bash-read-anchor and bash-write-anchor, and each replayed segment names its provenance as bash-heredoc, bash-cat, or bash-write.

On a real shell-first transcript, a python tool written through the heredoc-then-run shape went from no recoverable history to all 33 lines recovered verbatim.

plan: a correctness fix

csift bound a session to its plan only through the plan_mode attachment. Claude Code itself binds by the first record carrying a valid slug. On a forked clone, where attachments are stripped but slug records survive, csift answered "no plan" for a session whose plan Claude Code will re-inject. Both binding laws now apply, in that precedence order. Rows carry binding_source (plan_mode or slug-only) and a minted_at_compaction flag, and the plansDirectory setting from settings.json is honored.

list: clone lineage

A transcript whose first timestamped record is a compaction boundary was minted by copying another session at that compaction. That is the signature of a background-job fork: uuids preserved, timestamps that predate the file, slug stripped. The rule produced zero false positives on a real 61-file project directory. The list row now annotates the fork and names the origin session, and JSON gains is_clone, clone_of, and clone_boundary_uuid. One corollary is documented alongside. Until you scope it away, a clone double-counts its inherited records on every spanning surface.

status: live lanes and open tasks, not floods

Child lanes gain a generating state. A lane whose newest record is younger than 300 seconds, and whose last assistant stop_reason is not end_turn, is treated as mid-generation. The threshold comes from measurement. Record gaps inside a live lane reach 295 seconds at p99.9, while dead lanes sit 31+ hours out. The old 15-second mtime window misread one lane in 17, and stop_reason alone would have called 73% of dead lanes live, so the two conditions gate together and the mtime-based active state is retired.

Settled lanes now fold to a single count, so live work stays visible instead of drowning under hundreds of finished rows. A new tasks section reads the harness task list, printing open tasks with their blockers (in_progress first) and folding completed ones to a count.

search: required-needle prefilter

A pattern with metacharacters now derives a necessity-only literal set from its parsed structure. An alternation contributes a gate only when every branch demands a safe needle, so the gate can never drop a true match. The motivating query, TodoWrite.*legacy|legacy.*TodoWrite over every session, runs 1.70x faster in wall time with about half the CPU, and the output is byte-identical. A plain pattern containing spaces now anchors on its longest whitespace-free run.

files + recover: the file-history snapshot instrument

Claude Code rewrites its settings files in-process, with no tool record, on /model, /config, and plugin toggles. Measured on the corpus, half of all settings.json mutations are invisible to the tool stream. One such write silently deleted a freshly-edited key while recover replayed the file with the key still present, calling a state that never existed on disk "100% complete". Claude Code's own per-prompt snapshot version sequence is now read as an instrument against exactly this.

At every version change, recover compares the replayed buffer to mtime-verified snapshot content and rebases on divergence, recording an authoritative external_write boundary. A version jump with no tool write in between discloses the same boundary even when the content is gone. files emits external write timeline rows carrying the version transition and the uncovered interval. The scope is hard-limited to the settings family (.claude/settings*.json) on purpose. The tracked set spans 1701 corpus paths against 11 settings-family ones, and reporting all of them would flood every timeline. The version counter also resets mid-session (148 real cases), which makes @vN store names collide across a reset, so versions are segmented into generations and unverified blobs are refused.

search: role selectors speak LLM-visibility

Putting user.unsent under -t user broke a 0.7-era consumer. A superseded draft 12 seconds before the real submit poisoned a last-human-touch hook. Selectors now come in three forms. A bare role like -t user selects only the role's LLM-visible leaves. A glob like -t 'user.*' selects everything under the prefix. Intermediate prefixes and full leaf paths keep their full sets, so -t harness.compaction still reaches the boundary. Exactly two leaves are invisible, and both were verified with instruments. user.unsent is invisible because Claude Code's own preservedMessages accounting excludes every draft uuid. harness.compaction.boundary is a metrics-only record with no message at all. The practical effect is that -t user restores the 0.7 contract.

SKILL and docs

The staleness guard is now mechanical. Run csift --version at first use after any compaction, and a mismatch means the in-context copy is a stale echo. The frontmatter description gains the corpus-first trigger, which means live sessions need /reload-skills once. Two stale claims are fixed, and SPEC carries the full v0.9.4 ledger.

Verification

The full suite is green on all 8 release targets, at 1001 unit and 521 end-to-end tests on the unix lanes. Line coverage is 95.5%, 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 gate work caught two real risks before release. cat <<< words was briefly classified as a file read, and here-strings now refuse. A boundary record that merely references a uuid through logicalParentUuid could have joined as a clone origin, and now cannot. The Windows lanes caught a separator-naive test assertion, with product behavior correct on both platforms. All binaries were built with path-privacy remapping and audited for path leaks.

v0.9.3

Choose a tag to compare

@wdhwg001 wdhwg001 released this 01 Sep 06:32

Everything since v0.9.1: two new labels, a token-accounting correction, and the documentation catch-up that motivated the extra patch. (v0.9.2 was tagged but never released; this release carries its changes. Saved numbers move in two places, both on purpose: label censuses split, and stats token totals drop.)

New in 0.9.2

  • Label agent.thinking.narration (taxonomy -> 27). Since at least Claude Code 2.1.170 the API can return a SECOND thinking block in an assistant message: a one-sentence, user-language summary of the reasoning beside it, distinguished only by a tag encoded inside the base64 signature (clients 2.1.241+ render it dim under the hint word summarized). csift counted these as reasoning. Classification is by signature alone, whole-string decoded, with every failure path degrading to plain agent.thinking; the decode is byte-gated so large-corpus search keeps its previous speed. Hits carry a [narration summary] marker; verbatim never replays narration; -t agent.thinking still selects both leaves, and pure reasoning is -t agent.thinking -T agent.thinking.narration. Historical records re-classify by design (narration exists on disk from 2026-06-10), conserving the label-census sum.
  • Label user.unsent (taxonomy -> 28). A message that was sent, esc-recalled into the input box, edited and re-sent leaves the ORIGINAL on disk, sharing the resend's parentUuid - previously collapsed to a bare count. Drafts are now searchable and censusable under their own leaf: a matching draft renders as its own annotated unit (<tok>-draft, JSON superseded_draft:true, null turn_index), turn numbering is untouched, and user.message counts are unchanged. Measured limits documented: a recalled-then-abandoned message has no resend sibling and is undetectable; a QUEUED text edited before dispatch never becomes a user record at all (the bytes survive only in queue-operation lines, csift non-records).
  • stats narration census: narration_blocks per model (block counts only - the token split is not derivable from the jsonl) and unknown_thinking_tags (a new API tag value surfaces without a csift release).

Fixed in 0.9.2

  • stats token sums: Claude Code repeats the identical message.usage object on every per-block record of one API message, and stats summed per record - an over-report measured at 2.2x to 3.5x per field and model. Sums now dedupe per transcript by message.id (per-field MAX, immune to the compaction-replay shape that rewrites an id with zeroed usage); id-less records count individually; the scope TOTAL still sums transcripts. Printed totals drop accordingly.

Fixed in 0.9.3

  • The root help's hand-written SUBCOMMANDS block still listed eleven commands, contradicting the generated list one screen below: status and wait join it and the span-default enumeration.
  • The status/wait help described owner-process liveness as a signal-0 probe - a mechanism that never shipped. Corrected to the ps-based probe with the /proc fallback.
  • The --siblings policy text gains the narration cap of 1, in the help and every reference document.
  • README catches up two releases: thirteen subcommands with status and wait rows, a live-status highlight, Quickstart rows for status / wait / user.unsent / the label census, and the 0.8.1+ flags in their rows. SPEC gains per-command sections for status (6.13) and wait (6.14) and rewrites its stats section to the deduped token accounting; SKILL's superseded-draft bullet and JSON reference catch up.

Verification

  • 965 unit + 496 e2e; line coverage 95.43% (above the 0.9.1 baseline); a scoped mutation census over the touched files (602 mutants, new code fully killed, residue classified); the narration decode byte-gated back to noise-level cost on a 685MB transcript. 0.9.3 changes no behavior (help strings and documents only); the full suite ran green on each of its three commits.
  • All eight platform binaries built at the v0.9.3 tag with the full test suite green on every Linux and Windows lane; macOS binaries built with path-privacy remapping.

Full details: CHANGELOG 0.9.2 + 0.9.3 and the SPEC section 6 ledgers.

v0.9.1

Choose a tag to compare

@wdhwg001 wdhwg001 released this 30 Aug 07:53

A new command class: status and wait, the live-truth pair. Every other command answers "what happened" reproducibly; these two answer "what is happening NOW", are point-in-time, and are explicitly non-reproducible - a deliberate, documented departure from the forensic contract.

New

  • csift status <target>: one-shot liveness verdict for a session - running | waiting-children | waiting-hitl | idle-eot | stale-dead | unknown - from a three-way join, never a single-surface inference: the harness session registry (transition-writes only, never a heartbeat), the transcript tail state machine (an unreturned tool call at the tail = a tool in flight), and a ps-based owner-pid probe guarded against pid reuse by the process start time. Child liveness joins each subagent transcript's own tail with the incremental workflow journal (started minus result = agents in flight). The elicitation sidecar covers human-in-the-loop blocks. Every verdict ships its evidence rows, and every degradation is stated in the output.
  • csift wait <target> --until COND[,...]: block until a condition fires, first hit wins. The closed condition set: stop, hitl, auq, notification[:REGEX], tool:NAME[:REGEX], write:PATH_RE[:LINE_RE], verdict:V. STRICT post-start baseline semantics: only bytes appended after the watch starts count as events; history is search's job. A readiness line on stderr makes scripted waits race-free against their own trigger. Polling is incremental (byte offsets, torn tails held) and adaptive (200ms floor to 2s ceiling); lanes and the sidecar born mid-wait join the watch automatically.
  • Exit codes: wait exits 124 on --timeout expiry (the GNU timeout convention) - the ONE documented exception to the crate's 0-vs-non-zero exit law; it applies to no other command.

Fixed (0.9.1)

  • Pid liveness on busybox-ps hosts (Alpine and friends): busybox ps rejects -p and the lstart field, so a live session's pid read as dead and status reported stale-dead. The probe now falls back to /proc/ on Linux when the ps form fails, with the reuse-guard skip disclosed. Found by the release matrix's musl test lanes; v0.9.0 is the same feature set without this fix and ships no binaries.

Verification

  • 957 unit + 481 e2e; the six design acceptance probes plus discovery, degradation, baseline-law, and scoping rounds; line coverage 95.39% (above the 0.8.2 baseline); a scoped mutation census over the new modules with survivors converted or classified.
  • All eight platform binaries built at the tag with the FULL test suite green on every Linux and Windows lane; macOS binaries built with path-privacy remapping.

Full details: CHANGELOG 0.9.0 and the SPEC section 6 ledger.