Repository navigation
Description
Summary
ccstatusline appears to significantly overcount cumulative token usage for some Claude Code sessions.
In my current session, ccstatusline reports roughly 482.4M total tokens / 480.4M cached tokens, while Claude Code's own /usage output and an independent deduplicated transcript calculation are both around 225–230M.
The transcript contains multiple message.usage records for the same message.id (likely streaming snapshots). It looks like the current deduplication logic does not fully collapse those records before accumulating token metrics.
Environment
- ccstatusline: 2.2.30
- Platform: Windows 11
- Shell: Git Bash / MINGW64
- Claude Code session transcript: standard
~/.claude/projects/.../*.jsonl
ccstatusline output
Ctx: 123.7K Total: 482.4M 99.1% 163.9 t/s
In: 5.5K Out: 2.1M Cached: 480.4M
Claude Code /usage
Usage by model:
claude-haiku-4-5: 1.2k input, 25 output, 0 cache read, 0 cache write
claude-opus-5-5: 27.8k input, 946.0k output, 224.4m cache read, 3.1m cache write
claude-sonnet-5-5: 4 input, 547 output, 11.2k cache read, 12.4k cache write
That is roughly 228M total, not 482M.
Transcript evidence
The session transcript has 2813 records with message.usage:
jq -c 'select(.message.usage != null)' session.jsonl | wc -l
# 2813The same message.id frequently appears multiple times:
8 msg_011Cfj94xayLVkG7M8mPmLXT
6 msg_011Cfj9heNsaezMeM2q6MBgH
6 msg_011CfiMDW5RG4tHvwpwaXbWE
6 msg_011CfhEbCsrhbGqC9m7adGNg
5 msg_011CfjDDEiXTCHwRsVyQzRvc
...
If I deduplicate by message.id and keep the last usage snapshot for each message:
jq -s '
[
.[]
| select(.message.usage != null and .message.id != null)
]
| group_by(.message.id)
| map(last)
| reduce .[] as $x (
{
input: 0,
output: 0,
cache_read: 0,
cache_write: 0
};
.input += ($x.message.usage.input_tokens // 0)
| .output += ($x.message.usage.output_tokens // 0)
| .cache_read += ($x.message.usage.cache_read_input_tokens // 0)
| .cache_write += ($x.message.usage.cache_creation_input_tokens // 0)
)
| . + {
total: (.input + .output + .cache_read + .cache_write)
}
' session.jsonlI get:
{
"input": 2670,
"output": 894269,
"cache_read": 226139692,
"cache_write": 3157357,
"total": 230193988
}This is much closer to Claude Code's own /usage output.
Suspected cause
Looking at src/utils/jsonl-metrics.ts, cumulative token accounting currently depends on stop_reason / transcript state to decide which usage records to accumulate. In this transcript, multiple records for the same message.id still appear to be counted.
It may be safer to deduplicate token-usage snapshots by message.id (or by the most appropriate request/message key), while still preserving the existing logic for context length, compaction boundaries, sidechains, and API error records.
A possible rule could be:
- Group usage records by
message.id. - Prefer the last record with non-null
stop_reason. - If none has a non-null
stop_reason, use the last usage snapshot. - Accumulate token totals only once per logical message.
Expected behavior
The cumulative token widgets should be close to Claude Code's own /usage totals for the same session, rather than roughly 2x higher.
I can provide a sanitized transcript sample if useful.
Activity
z1pp090 commentedon Oct 11, 2026
This looks like the same root cause as #549: Claude Code writes one transcript row per content block, all sharing a
message.idand carrying the sameusage, and the accumulator sums each row.I opened #595, which collapses rows by
message.idand counts the last row of each message (it carries the final usage). On 23 local transcripts (7,600 API calls) it takes cache read from 3.15G to 1.84G (1.71x), and output from 12.1M to 5.6M (2.18x). Those ratios are in the same range as your 482M vs ~228M.Your
jqcheck (group bymessage.id, keep the last snapshot) is the same rule the PR applies, so it may be worth running your session against it. Related to #595.