DEV Community

Cover image for 80% of my Claude Code bill was output that never left the context
hyuga
hyuga

Posted on

80% of my Claude Code bill was output that never left the context

I had a number I could not explain.

ccusage told me what I spent. Good tool, does its job. But the biggest line in it was cache_read — the charge for re-reading the whole conversation on every single turn — and nothing told me what I kept re-reading.

So I wrote nenpi (燃費, Japanese for "fuel economy") to find out. Zero dependencies, reads the JSONL transcripts already sitting in ~/.claude/projects/, uploads nothing.

The first run reframed the whole problem.

Residency cost

## Residency cost (tokens injected x turns that followed) = what cache_read really is
    resid  share  calls injected     avg  imgs  tool
    1669M  81.4%   5586     2.4M     432     0  Bash
     231M  11.3%    229     0.3M    1474   163  Read
      43M   2.1%     64     0.1M     950    34  mcp__claude-in-chrome__computer
      17M   0.8%    372     0.0M      55     0  Write
Enter fullscreen mode Exit fullscreen mode

Bash output was 2.4M tokens of actual text. Its residency cost was 1669M — 81% of everything I spent.

Residency cost = tokens injected × turns that came after. A tool result is not charged once. It is charged once per turn that follows it. Dumping a log file on turn 5 of a 300-turn session is a completely different purchase from doing it on turn 299, and no cost-per-day chart will ever tell you that.

Once you can sort by that column, the thing worth fixing stops being a guess.

The counting bug that makes most of these numbers wrong

This is the part I would want to know if I were writing my own token tool.

Claude Code splits one API response into a separate JSONL line per thinking / text / tool_use block — and copies the same usage object onto every one of them.

Sum per line and you count the same charge several times over. Measured on my machine: +82%.

The same split breaks parallel-call detection. From a single line you cannot tell how many tools one response called, so a per-line count pins your bundling rate at 0.0% forever, no matter what the model does.

nenpi deduplicates by message.id and regroups by requestId before it counts anything.

Then it told me my own hook was useless

nenpi is also three Claude Code hooks — the point where measuring turns into a smaller bill.

Hook Event What it does
nenpi hook pre PreToolUse A full-file Read of a large file gets cut to the first 400 lines, with a note to Grep first. Full text would sit in the context and be re-sent every turn.
nenpi hook prompt UserPromptSubmit Shows what re-reading the current context costs per turn. It never says "your context is too long" — that claim did not hold up in the measurements.
nenpi hook post PostToolUse When the same tool has run one call at a time for several turns straight, says so once, at the moment it happens.

And nenpi effect exists to check whether a nudge like that does anything at all.

Week-to-week comparison confounds — spend fell, but was that your change or an easier week? So effect compares, inside the same session, the turns immediately after a nudge fired against ordinary turns. Whatever the work was, it applies to both sides and cancels out.

## "bundle them" (hook post) — did the next turn actually bundle?
  fired                             80 times
  bundled on the next turn           8 / 79   10.1%
  normal turns, same sessions      201 / 6401   3.1%
  difference                    +7.0pt
  → It does not work. Reword it, or take it out.
Enter fullscreen mode Exit fullscreen mode

My own tool, telling me to rewrite my own nudge.

That turned out to be the feature I use most. Not "how much did I spend" — "the thing I was sure about is not true."

What is measured and what is estimated

Being straight about this matters more than the numbers looking precise.

Measured, from usage: token counts, tool call counts, timestamps, hook durations, is_error flags, compaction events. Dollar figures come from Claude Code's own costUSD, not from a model of mine.

Estimated: BYTES_PER_TOK = 3.5 for the byte→token conversion, IMG_TOK = 1600 per image block, and the weights that turn token classes into input-token equivalents. quality prints a calibration residual against the real costUSD so you can see how far off the weights are on your plan. Mine runs about 1%.

So: charges are measured, attribution is estimated. Residency cost sits on the estimated side. It is still the most useful number here, because nothing else points at which output is the expensive one — but read it as an estimate.

Try it

npx @hyuga/nenpi report
Enter fullscreen mode Exit fullscreen mode

Node 18 or newer. MIT. Source: https://github.com/hyuga611/nenpi

There are two more commands I lean on:

  • quality — cutting tokens is easy if you are allowed to make the agent worse. It puts fuel, intelligence and speed on one screen (rework rate, tool failure rate, user correction rate, wasted-Read rate, turns per prompt, wall time per prompt) so a "win" has to hold on all three.
  • errors — a rising tool failure rate is not evidence the model regressed. Permission denials, EPERM, a human pressing stop all raise it. This splits environment-caused failures from model-caused ones, broken down by model × effort.

One caveat worth repeating: the transcript format is undocumented and it changes. nenpi pins its counting to message.id and requestId, which have been stable, but a format change can silently make a metric wrong. If a number looks impossible, it probably is — please open an issue.

Top comments (0)