Skip to content
NickAme03Public

About

Measures how Claude Code usage counting rules differ on a real corpus: deduplication, usage.iterations aggregation, and models present

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ccledger

Measures how the counting rules for coding-agent token usage differ on a real corpus, so a tool that reports totals can be checked against data instead of against its own source code.

Tools that read ~/.claude/projects disagree about three things, none of which is documented: how to combine several records that share one dedup key, whether usage.iterations is already aggregated by its parent block, and which models exist. Codex sessions add a fourth: a running total sitting next to a per-turn delta under near-identical names. Each disagreement moves the reported number. ccledger applies every rule to the same corpus and prints the distance between them.

No dependencies outside the standard library. It reads and prints; it writes nothing.

Running it

python ccledger.py                        # whichever corpora exist on this machine
python ccledger.py /some/other/path       # a different Claude Code corpus
python ccledger.py --codex /some/path     # a different Codex corpus

With no arguments it reports on ~/.claude/projects and ~/.codex/sessions, skipping whichever is absent.

Python 3.9 or later. On Windows, py -3 ccledger.py.

Sample output

Abridged, from a 201-file corpus:

corpus: C:\Users\you\.claude\projects
JSONL files: 201 | lines with usage: 9,425 | unreadable lines: 0
groups (requestId, message.id): 3,623

==============================================================================
check 1  usage.iterations: does the parent block aggregate the iterations?
==============================================================================
  records with iterations   : 8,922
  records with more than one: 23
  of those, mixed models    : 23

  for records with more than one iteration, which rules explain the parent.
    parent == last         23   100.00%
    explained by exactly one rule: 23 (100.00%)

  tokens in iterations that are not the final one, by model.
    model                              input     output   cache_creation    cache_read
    claude-fable-5                    90,186      8,297          152,481     1,436,662

==============================================================================
check 2  deduplication: how far apart the counting rules land
==============================================================================
  class          groups  multi-line   differing   multi-file  non-monot.
  main            3,237       2,598           0          207           0
  subagents         386         279         229            0           0

  ratio against max_record, which is 1 by construction:
    rule                   input        output  cache_creation        cache_read
    no_dedup             2.0952x       2.8655x         2.7227x           2.6307x
    sum_distinct         1.4074x       1.0003x         1.0725x           1.0150x
    max_record           1.0000x       1.0000x         1.0000x           1.0000x
    max_field            1.0000x       1.0000x         1.0000x           1.0000x
    first_line           1.0000x       0.9511x         1.0000x           1.0000x
    last_line            1.0000x       1.0000x         1.0000x           1.0000x

==============================================================================
check 3  models present in the corpus
==============================================================================
  model                                records         input       output
  claude-fable-5                         5,616     4,536,794    7,264,717
  claude-opus-5                          2,250        13,121    3,889,457
  [...]

==============================================================================
check 4  codex: is total_token_usage a running total?
==============================================================================
  sessions carrying tokens  : 182
  never decreasing          : 182  (100.0%)

    reading                                           total tokens
    sum of total_token_usage, line by line          30,074,985,351
    final total_token_usage, once per session          509,045,554
    sum of last_token_usage (per-turn deltas)          511,449,773

    summing the running total inflates by 59.08x
    the deltas rebuild the final total to within 0.47%

  arithmetic of the nested fields, over 4,903 per-turn records:
    input + output == total           4,846  ( 98.84%)
    cached_input <= input             4,903  (100.00%)
    reasoning_output <= output        4,903  (100.00%)

What this particular corpus says, as an example of how to read it:

  • The parent block equals the last iteration and, since every one of the 23 records is explained by exactly one rule, it is not the maximum. The tokens of the attempt that did not serve belong to a different model and are attributed to none.
  • Only the output column moves between the sensible dedup rules. Input and cache counts are known when a message starts; the output is not, so keeping the first line keeps a snapshot taken before the message finished, and loses 4.9%.
  • max_field and last_line both land exactly on max_record, so on this corpus those three rules cannot be told apart by their totals. non-monot. is zero, which is why.
  • Every differing group is under subagents/: none of the 3,237 top-level groups has rows that disagree. A corpus without subagent traffic would show no difference at all between the rules.

The output totals in check 1 and check 2 differ because they count different things: check 1 sums every record carrying iterations, check 2 sums one entry per dedup key.

What it checks

1. usage.iterations. Some records carry an array of complete usage objects alongside the parent block. A tool that summed them on top of the parent would double count; one that ignored them would lose tokens if the parent does not aggregate them. The check reports which rule explains the parent, and counts how many records are explained by exactly one rule: when several rules match, the corpus is not telling them apart rather than confirming all of them.

It also reports the tokens carried by iterations that are not the final one, grouped by their own model field. The parent block carries a single message.model, so those tokens are attributed to no model by any tool that reads that field alone.

2. Deduplication. Claude Code writes a streaming message as several lines sharing one (requestId, message.id) key, and the earlier lines carry a partial usage snapshot. The check applies six rules to every group and prints the totals side by side, with the ratio against max_record:

rule what it does
no_dedup sums every line
sum_distinct sums the distinct usage fingerprints
max_record keeps the record with the highest total
max_field takes the maximum of each field separately
first_line keeps the first line seen
last_line keeps the last line seen

Results are split between top-level transcripts and subagent transcripts, because the rules can agree on one and disagree on the other. The non-monot. column counts groups where a later record in the same file carries a lower total than an earlier one: when it is zero, last_line and max_record coincide on that corpus, and a tool cannot be distinguished by its totals alone.

3. Models. Lists the models the corpus actually contains, to compare against a tool's price table. A model missing from that table is usually priced at zero without any error being raised.

4. Codex running totals. Codex session files put two near-identical objects side by side under payload.info: total_token_usage is a running total for the whole session, last_token_usage is the delta for that turn. The names do not say so. The check measures whether either one ever decreases across a session, which is what tells a running total apart from a per-turn measure, and reports what summing the wrong one costs. On the corpus this was written against, that is a 59x overcount.

It also checks the arithmetic of the nested fields, since cached_input_tokens and reasoning_output_tokens are subsets of their parents rather than additions, and reports records whose total is not explained by input plus output, which a tool adding up only the detail fields would lose.

Reproducibility

A corpus grows while it is being measured, because the session running the measurement writes to its own transcript. ccledger fixes each file's size before reading it, so two consecutive runs over the same state produce identical output, and reports how many files changed underneath. Numbers meant to be quoted should come from a run that reports no growth.

Tests

python -m unittest discover

The rules agree on almost all real data, so the tests build corpora on purpose to make them disagree: a group whose last record drops below its own maximum, a group whose per-field maxima sit on different records, iterations that grow on every field and therefore cannot distinguish last from max. A test passing on data where two rules coincide would not show that the tool tells them apart.

What it does not do

It does not price anything and it does not tell you which rule is correct. Which records are actually billed is not visible in a transcript: it is visible on an invoice. The tool reports how far apart the rules land, and the choice between them stays an argument about the protocol, not about this corpus.

License

MIT.

About

Measures how Claude Code usage counting rules differ on a real corpus: deduplication, usage.iterations aggregation, and models present

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages