Skip to content

Token widgets overcount usage when transcript contains multiple usage snapshots for the same message.id #664

Closed as duplicate

Description

@hahahehe-coder

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
# 2813

The 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.jsonl

I 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:

  1. Group usage records by message.id.
  2. Prefer the last record with non-null stop_reason.
  3. If none has a non-null stop_reason, use the last usage snapshot.
  4. 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

  1. z1pp090 commented on Oct 11, 2026

    @z1pp090
    Contributor

    This looks like the same root cause as #549: Claude Code writes one transcript row per content block, all sharing a message.id and carrying the same usage, and the accumulator sums each row.

    I opened #595, which collapses rows by message.id and 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 jq check (group by message.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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions