Repository navigation
fix(session-flow): audit-sessions token metrics lost on current transcripts (usage_split canary) #6785
Description
Problem
/session-flow:audit-sessions reports tokens.main and tokens.sub as unavailable (canary lost) across the whole store, so the token lens never fires. The drift section names the lost canary as invariant:usage_split.
Evidence
One sweep on Claude Code 2.1.296: 683 sessions, versions 2.1.241 through 2.1.296.
- Metrics table:
tokens.mainandtokens.subwereunavailable (canary lost), n=683. - Drift:
canary-lost: invariant:usage_split, plus 133 vanished key paths and 11 unknown record types (mode,atis-latch,ai-title,file-history-delta,cost-state,frame-link,relocated, and others). Unchecked: "tokens.main: unavailable: a drift canary feeding it is lost".
A related question surfaced in the same sweep: hooks.stop_ms (median 4,472 ms, 600 of 683 sessions over threshold) may include hooks that run with asyncRewake: true, such as the testing plugin's test-judge collector. Those hooks do not block turn end. If the metric sums or includes them, it overstates the blocking cost.
Proposed approach
- Find which transcript change broke the
usage_splitinvariant, and update the shared reader and its canary. - Check whether
hooks.stop_mscounts async hooks, and exclude them or report them separately.
Acceptance criteria
-
tokens.mainandtokens.subproduce values on transcripts from 2.1.296. - The
usage_splitcanary holds on current transcripts, or is replaced, with a fixture from the current format (no raw transcript content, per the eval-case transcript rule). -
hooks.stop_mseither excludes async hooks or documents that it includes them.
Related
Transcript source
A run does not need the local transcript store: it can produce current-format transcripts itself with the installed CLI (a short claude -p session that spawns one subagent and runs one async Stop hook) and probe their structure, printing key names and numbers only.
Activity
github-actions commentedon Oct 10, 2026
Proposed work class: work-class: scoped
Basis: automated intake triage; the lanes App applied this class.
Size: Medium
Complexity: Medium
Uncertainty: Knowable
Flags: none
Blocked by: none
Understood:
/session-flow:audit-sessionslosestokens.mainandtokens.subbecause theusage_splitcanary trips on current (2.1.296) transcripts. Separately,hooks.stop_msmay includeasyncRewakehooks that don't block turn end.The approach and acceptance criteria are clear. Reading the shared reader and a current-format transcript would show which change broke the invariant. The probe needs a live
claude -prun, which the issue lane can't do, so part of the verification will happen outside this lane.Likely related: #4373 (listed in the issue). I found no duplicates among the open issues.
Automated triage, applied by the lanes App.
kyle-sexton commentedon Oct 11, 2026
Overnight worker: blocked, no PR opened. The token fix needs a look at current-format transcripts. This run was not allowed to read the local transcript store (
~/.claude/projects): the auto-mode classifier denied it, so I stopped there.State: no commits. The local branch
fix/6785-usage-split-canarywas never pushed, because it carries nothing yet.What I found without transcripts. These are inferences from reading the minified writer code in the installed Claude Code 2.1.296 bundle. They are not checked against a real transcript.
message_deltastream event arrives, the writer merges its usage into the message's running usage. Theinput_tokens,cache_creation_input_tokensandcache_read_input_tokensvalues are replaced whenever the delta carries a positive value. The merged usage is then assigned to every record already yielded for that message.message.idcan now legitimately differ on input and cache usage. That is exactly whatinvariant:usage_splitcounts (collect.py_usage_invariant). The invariant assumes streaming rewrites onlyoutput_tokens, and that assumption looks stale.transcript_reader.UsageLedger) keeps the merged values. That depends on which records are serialized after the merge.hooks.stop_ms:stop_hook_summaryis built with onehookInfosentry per Stop hook that reports progress, and it now also carrieshookCountandtotalDurationMs. I did not confirm whether anasyncRewakehook gets an entry or adurationMs.Next step (any agent with transcript read access):
message.idgroup, record whether input and cache usage differ, and which record position carries the merged (final) values. Print numbers and key names only.canaries.json(validated_against,basis).hooks.stop_ms, check one session that has anasyncRewakeStop hook to see whether itshookInfosentry carries the async run's duration. Then exclude that entry or document it.Generated by Claude Code
kyle-sexton commentedon Oct 11, 2026
Handoff note for whoever picks this up next. The overnight Claude run has stopped, and Kyle is moving issue work to Cursor.
Where a branch is listed it is pushed, so it can be taken over as is. A draft PR shows a red
ci-statusby design until it is marked ready.Generated by Claude Code
kyle-sexton commentedon Oct 19, 2026
This was generated by AI during triage.
Held for a person: The token fix needs current-format transcripts from the local store, and auto mode denied that read (2026-10-11). It needs the owner to allow transcript reads or to run it in an attended session.
kyle-sexton commentedon Oct 24, 2026
Released by the owner's HITL session (2026-10-24, C12): it waited only on a removed gate. Intake triage re-runs on it to set its fields, then admission takes it.
kyle-sexton commentedon Oct 24, 2026
Needs a run of
claude(eval, probe or /usage), which the issue lane cannot do (pr-pipeline.yaml:289-314). The owner's HITL session runs it on the operator machine; no owner action needed.github-actions commentedon Oct 25, 2026
The triage sweep dispatched intake triage for this issue (attempt 1 of 2).
melodic-automation-lanes commentedon Oct 25, 2026
The lanes App's pull request #10041 merged with
Refs:, so this issue stays open and nothing starts the lane on it again. work-unblock removedstatus: readyandagent-ready, so the hourly triage sweep sends it back to intake triage, which decides what work is left and admits it again.melodic-automation-lanes commentedon Oct 25, 2026
The work-resolve-issue lane stopped partway through this issue and needs a person.
Why, in the run's own words:
What the run says it did not do:
clauderun or reads of the local transcript store, and this lane has neither. The issue comment of 2026-10-24 assigns it to the owner's HITL session.Run: https://github.com/melodic-software/claude-code-plugins/actions/runs/40218329611
To retry, remove
needs-humanand addagent-readyagain.