Conversation
Claude Code writes an assistant response to the transcript once per content block - a `thinking` line, a `tool_use` line, and so on - and every one of those lines repeats the same `usage` object, the same `message.id` and an increasing `apiBlockIndex`. The transcript fallback counted each line, so a two-block response was billed twice: Tokens Input / Output / Cached / Total and the speed widgets' request count all read roughly 2x on current transcripts. Continuation blocks are now skipped - `apiBlockIndex` when the transcript carries it, a repeated `message.id` otherwise - and in the speed path the later block only stretches the request's interval and takes its final usage, so a streamed response (`stop_reason: null` lines followed by the real count) is still counted exactly once. The live `context_window` path in the status JSON is untouched, and so is `contextLength`: repeats carry identical usage, so dropping them cannot move the most-recent-usage figures. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Sep 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
BLUF
thinking,tool_use, …). Each of those lines repeats the sameusage, the samemessage.id, and an increasingapiBlockIndex— and the fallback counted every line.Tokens Input/Output/Cached/Total, and the speed widgets'requestCount/ token totals.apiBlockIndex > 0when present, a repeatedmessage.idotherwise — so one response is counted once.context_windowpath andcontextLengthare untouched; streamed responses (stop_reason: nullthen the real count) are still counted exactly once.bun run lintclean,bun run buildclean, and the patched build's numbers now match an independent per-message tally of real transcripts exactly.The evidence
Two consecutive lines from a live transcript, one API response:
apiBlockIndex01message.idmsg_011CfAdDY4…msg_011CfAdDY4…(same)message.stop_reasontool_usetool_useusage.input_tokensusage.output_tokensusage.cache_read_input_tokensthinkingtool_useBoth lines carry
stop_reason, so the existinghasStopReasonFieldfilter keeps both and the response is counted twice.Rendering the same session's transcript through both builds, side by side:
An independent per-message tally of that transcript (a separate script, deduping on
message.id) gives 212 / 16.7M / 59.7k, matching the patched build exactly.The fix
isUsageContinuationBlock()insrc/utils/jsonl-metrics.ts, used by both collectors:lastCountedMessageIdis only updated when an entry is actually counted, so the in-flight format (severalstop_reason: nulllines, then the final line with the real usage under the same id) still counts that final line once.requestCountbecomes responses, not lines.Untouched on purpose:
context_windowpath — it never had this problem.contextLength/ most-recent-usage — repeats carry identical usage, so dropping them cannot move those figures.src/utils/jsonl-blocks.ts— it only reads timestamps of usage-bearing lines for block-window activity, so theBlock Timerwidget is unaffected.Tests
New cases in
src/utils/__tests__/jsonl-metrics.test.ts:contextLength);message.idfallback, for transcripts withoutapiBlockIndex;bun test: 2360 pass / 3 fail. The failures are pre-existing flaky TUI/subprocess cases in this environment (fetchUsageData5s subprocess timeout,TerminalWidthMenu,UsageTimezoneEditor); the failing set varies run to run - an unmodifiedmainhere fails 7, this branch 3 - and none of them is in metrics or token widgets.bun run lintandbun run buildare clean.Relationship to other PRs
🤖 Generated with Claude Code