DEV Community

Andre Burgstahler
Andre Burgstahler

Posted on Originally published at Medium

Where Did My Claude Usage Go? Auditing Your Agent's Hidden Token Tax

This article first appeared on Medium: https://medium.com/@r3dtuxedo/where-did-my-claude-usage-go-auditing-your-agents-hidden-token-tax-95d6ab17b8b6

Most "token leaks" aren't bugs. They're overhead you pay on every single turn. Measure it first, then cut it.

TL;DR

  • Every request re-sends the system prompt, the project context and the whole conversation. A "fix this test" turn with ten tool calls reads your full prefix about ten times.
  • The cache keeps that cheap until it expires. On both Opus 5.5 and Sonnet 5.5, rewriting the cache costs 40 times what reading it costs, which is why "continue" after a long break hurts.
  • Measure before you cut: the session logs, ccusage, /context and /usage. Then fix one leak at a time (cold re-reads, MCP tools, instruction files, skill listings, prefix breakers) and rerun the numbers after a few days.

In April I wrote Cut Your Claude Code Bill by Up to 95%. That piece was about the price of a token. This one is about the number of tokens, and specifically the ones you never typed.

I keep seeing the same post in different subreddits this week. Someone on r/ClaudeCode keeps hitting the five-hour limit and writes: "I seem to have some sort of 'token leak'." Nobody else is using the account. A Pro user on r/ClaudeAI ran /compact right after a reset and watched usage jump to 11% "without actually getting any work done." Later, typing "continue" after a reset took them from 0% to around 40%. An OpenCode user whose quota used to last all day now burns through it in 30 minutes. Meanwhile r/ClaudeCode asks why anyone runs out at all.

None of these are bugs. They're the invisible part of every request your agent sends. Here's how I audit that part before I start cutting.

None of these are bugs. They're the invisible part of every request your agent sends.

What one turn actually costs

The model remembers nothing between requests. Every request re-sends everything, and Claude Code's prompt caching docs describe three layers:

  1. System prompt: core instructions and tool definitions.
  2. Project context: CLAUDE.md, auto memory, unscoped rules.
  3. Conversation: every message and tool result so far, plus your new message.

Two details matter more than the rest.

A turn isn't one request. Each time Claude uses a tool, Claude Code sends another request with the whole context plus the new tool results. A "fix this test" turn with ten tool calls reads your full prefix about ten times.

The cache is a prefix match, and it expires. Unchanged content is billed at the cached rate, and a change anywhere in the prefix recomputes everything after it. On a Claude subscription within plan usage, the main conversation gets a one-hour cache lifetime. On an API key, a cloud provider, or once you draw on usage credits, it's five minutes. Subagents and compaction requests get five minutes even on a subscription.

Diagram of what every request re-sends. One request has three layers: system prompt (core instructions and tool definitions), project context (CLAUDE.md, auto memory, unscoped rules) and conversation (every message and tool result so far, plus the new message). A
Three layers, re-sent on every request. A turn with tool calls is many requests.

Now the prices, from Anthropic's pricing page today:

Per million tokens Base input 1h cache write Cache read
Claude Opus 5.5 $4.00 $8.00 $0.20
Claude Sonnet 5.5 $2.00 $4.00 $0.10

On both models, rewriting the cache costs 40 times what reading it costs. Subscription limits aren't published in dollars, so I use API list prices only as a yardstick. The ratio is the point.

Here's an illustrative example on Opus 5.5. Say a session has grown to 300,000 tokens of context. Each warm request reads that for about $0.06, so a ten-tool-call turn costs around $0.60 in cache reads alone. Now you leave it for lunch. When you come back, the cache is cold, and the first request writes all 300,000 tokens again at $8 per million: $2.40 before the model does anything useful. That's the "continue took me to 40%" story in one line of arithmetic.

Horizontal bar chart for an illustrative 300,000-token Opus 5.5 session at API list prices. One warm request, a 300K cache read at $0.20 per million: $0.06. A warm turn with ten tool calls: $0.60. The first request after lunch, with the cache cold and 300K tokens re-written at $8 per million: $2.40. Rewriting the cache costs 40 times what reading it costs.
The 300,000-token example as a chart: $2.40 before the model does anything useful.

Measure first

The raw logs

Claude Code already records all of this. By default, transcripts live at ~/.claude/projects/<project>/<session-id>.jsonl and are kept for 30 days (cleanupPeriodDays changes that). Every assistant entry has a message.usage object with four numbers: input_tokens, cache_creation_input_tokens, cache_read_input_tokens and output_tokens.

This is the per-request view I use. One response can be logged several times while it streams, so the script keeps the last snapshot for each message and request ID:

# turns.py - usage: python3 turns.py ~/.claude/projects/<project>/<session>.jsonl
import json, sys
seen = {}
for line in open(sys.argv[1]):
    try: e = json.loads(line)
    except ValueError: continue
    m = e.get("message") or {}
    u = m.get("usage")
    if not u: continue
    seen[(m.get("id"), e.get("requestId"))] = (e.get("timestamp", ""), u)
print(f"{'time':20} {'input':>8} {'cache_write':>12} {'cache_read':>11} {'output':>8}")
for ts, u in sorted(seen.values(), key=lambda x: x[0]):
    print(f"{ts[:19]:20} {u.get('input_tokens',0):>8,} {u.get('cache_creation_input_tokens',0):>12,} "
          f"{u.get('cache_read_input_tokens',0):>11,} {u.get('output_tokens',0):>8,}")
Enter fullscreen mode Exit fullscreen mode

Find your most recent sessions with ls -t ~/.claude/projects/*/*.jsonl | head. The output looks like this (illustrative numbers):

time                    input  cache_write  cache_read   output
2026-10-19T08:00:06         3       21,000           0      400
2026-10-19T08:01:00         5          900      21,000      300
2026-10-19T10:30:00         5       95,000           0      200
Enter fullscreen mode Exit fullscreen mode

Look for two patterns. A steadily climbing cache_read means the context itself is the cost. A big cache_write spike after a gap means a cold re-read, a compaction, or something that broke the prefix.

ccusage

For the wider view, I use ccusage, a third-party CLI that reads those same local files. Your usage data stays on your machine.

npx ccusage@latest daily                  # every detected agent, by day
npx ccusage@latest claude daily -i        # Claude Code, broken down by project
npx ccusage@latest claude session         # which conversations were expensive
npx ccusage@latest blocks --recent -b     # 5-hour windows, last 3 days, per-model
Enter fullscreen mode Exit fullscreen mode

blocks is the one that matches the session limit, because it groups usage into five-hour windows. Since v18 it no longer has a live mode; ccusage statusline replaced that. ccusage also reads Codex and OpenCode data, so ccusage codex daily and ccusage opencode weekly work the same way.

There are two caveats. The costs are estimates, and ccusage only sees this machine. If a forgotten VPS session is the culprit, run it there too. And cache reads dominate the totals, so compare sessions with each other rather than with your plan limit.

Inside the session: /context and /usage

/context breaks down the current context window by category: system prompt, system tools, MCP tools, custom subagents, memory files, skills, and messages. It also gives optimization suggestions. The shape looks roughly like this (illustrative, not a real capture):

Context usage
  System prompt        ...
  System tools         ...
  MCP tools            ...   <- names and server instructions, unless loaded upfront
  Custom agents        ...
  Memory files         ...   <- CLAUDE.md / AGENTS.md and imports
  Skills               ...   <- the listing, after budget
  Messages             ...
Enter fullscreen mode Exit fullscreen mode

/usage is the one most people skip. Besides session totals, recent versions add a Prompt cache (main) line with your hit rate, misses, and whether the cache is warm right now, sometimes with a likely cause for the last miss. On Pro, Max, Team and Enterprise plans it also attributes recent usage to skills, subagents, plugins and individual MCP servers. It flags behaviours like long context or cache misses once they pass 10% of usage, and it lists your heaviest /loop and scheduled tasks. Press d or w to switch between the last day and the last week. It's computed from local history, so claude.ai usage doesn't show up there.

To measure your fixed overhead directly, run claude -p "hello" --output-format json and read usage. Cache creation plus cache read on that first request is roughly your per-session baseline. It uses a little of your plan, so don't script it in a loop. Run it once in an empty directory and once in your repo, and the difference is what your project adds.

Codex and OpenCode

In Codex, /status shows session token usage and remaining context, /statusline can show context stats in the footer, and codex debug prompt-input renders the exact model-visible prompt input as JSON. That last one is the most honest answer to "what am I sending?" you'll get from any tool. In OpenCode, opencode stats --days 7 --models gives totals including cache reads and writes, and --project "" scopes the report to the current project.

The five biggest leaks, and the fixes

1. Re-reads after the cache expires

This is the 40x multiplier. Leaving a large session idle for longer than its TTL and then typing "continue" is the most expensive word in agentic coding. Subagents are on a five-minute TTL, so long-running subagent work with gaps pays rebuild costs more often than you'd expect.

Fix: Treat a cold, large session as something to retire, not resume. I covered when to compact, clear, or hand off in yesterday's piece on session lifecycle (link), so I won't repeat it here. If you went down a dead end, /rewind truncates back to a prefix that's already cached instead of building a new one.

2. Tool and MCP definitions

The good news: Claude Code now defers MCP tool definitions by default through tool search, so at session start only tool names and server instructions load. The catch: tool search is turned off when ANTHROPIC_BASE_URL points to a non-first-party host. If you followed my earlier article and route Claude Code through a third-party endpoint, every MCP tool definition goes back to loading upfront on every request. On that setup, connecting or removing a server mid-session also invalidates the cache.

Fix: Check the MCP tools row in /context. Disable servers you aren't using in /mcp. Prefer CLIs like gh when they exist, because they add no per-tool listing at all. On a gateway, test whether ENABLE_TOOL_SEARCH works with your proxy before relying on it.

3. Instruction files

CLAUDE.md is read at the start of every session, and so are its @imports. Imports help you organise a long file but don't reduce its cost. Anthropic's guidance is under 200 lines per file. Two subtler points: a mid-session edit doesn't apply until /clear, /compact or a restart, and OpenCode reads ~/.claude/CLAUDE.md and .claude/skills by default, so your Claude setup can follow you into another harness.

Fix: Move workflow-specific instructions into skills or path-scoped rules, which load only when relevant. Confirm what actually loaded under Memory files in /context. In OpenCode, OPENCODE_DISABLE_CLAUDE_CODE_PROMPT and OPENCODE_DISABLE_CLAUDE_CODE_SKILLS turn that inheritance off.

4. Skills and plugin listings

Skills are cheap until you have dozens. Every skill's description sits in a listing that's present on every turn, whether or not it's ever used. The listing's budget scales at 1% of the model's context window, and plugins add their own entries.

Fix: Run /skill-doctor to see what each skill costs and how often you use it. /doctor estimates the listing's total cost. For skills you only ever invoke by hand, set disable-model-invocation: true so the description stays out of context, or set them to "name-only" in skillOverrides.

5. Compaction and other prefix breakers

/compact sends a separate request with your full history to produce the summary. With a warm cache that's mostly cheap reads. With a cold cache it reprocesses everything as uncached input, which is exactly what the r/ClaudeAI poster hit. Switching models also starts a fresh cache, because each model has its own. On most models so does changing effort mid-session, though Opus 5.5, Sonnet 5.5, Haiku 5.5 and Fable 5.1 keep the cache through effort changes on the API and subscriptions. Turning on fast mode and upgrading Claude Code each cost one uncached turn too.

Fix: Pick your model and effort level at the start of a session. Compact at natural breaks while the cache is warm, not mid-task. Read the "likely cause" on the /usage cache line when misses climb.

The one that isn't on your machine

On Pro and Max, the five-hour and weekly limits are shared across Claude and Claude Code. A long claude.ai chat with big attachments, the desktop app, mobile, and IDE sessions all draw from the same pool, and none of it appears in ~/.claude/projects or ccusage. Background work counts too: scheduled tasks and /loop send your full context every time they fire, even while you're away.

Audit map with three columns: leak, where to see it, fix. Leak 1, re-reads after the cache expires: the /usage cache line and cache_write spikes after a gap; retire cold, large sessions and rewind dead ends. Leak 2, tool and MCP definitions: the MCP tools row in /context; disable unused servers, prefer CLIs like gh, test ENABLE_TOOL_SEARCH on a gateway. Leak 3, instruction files: the Memory files row in /context; under 200 lines, move workflows into skills or path-scoped rules. Leak 4, skills and plugin listings: /skill-doctor and /doctor; disable-model-invocation or name-only. Leak 5, compaction and prefix breakers: the likely cause on the /usage cache line; set model and effort at the start, compact at natural breaks while warm. Off your machine, shared plan limits: claude.ai, desktop, mobile and other machines don't show in local logs; review scheduled tasks, loops and other machines.
The five leaks, plus the one that isn't on your machine, on one page.

Before/after checklist

Before you change anything, write down:

  • [ ] ccusage blocks --recent -b: your last few five-hour windows
  • [ ] ccusage claude session: your top three sessions by tokens
  • [ ] /context in a fresh session: the size of each fixed category
  • [ ] /usage: cache hit rate, attribution shares, flagged behaviours
  • [ ] claude -p "hello" --output-format json: baseline cache creation, empty directory vs repo

Then cut, one change at a time:

  • [ ] Disable unused MCP servers; check tool search if you use a gateway
  • [ ] Trim CLAUDE.md/AGENTS.md toward 200 lines; move workflows into skills or path rules
  • [ ] Run /skill-doctor; set rarely used skills to manual-only or name-only
  • [ ] Set model and effort at session start; stop switching mid-task
  • [ ] Retire cold, large sessions instead of typing "continue"
  • [ ] Review scheduled tasks, loops and other machines
  • [ ] Remember the claude.ai chats on the same plan

After a few days, rerun the first list and compare. If the numbers didn't move, the leak was somewhere else, and now you know where to look next.

I'm not quoting my own percentages here, because your setup isn't mine. The method carries over, though. Read the logs before you blame the model, and measure before you cut.


If this was useful, subscribe to Agents in Practice for one practical note on AI coding agents a week. Helping your team adopt Claude Code or Codex safely and without the surprise bill? I consult on exactly that: https://andre-burgstahler-9n5.pages.dev/.

This article was written with AI assistance and reviewed by the author.

Top comments (0)