Post

Where Claude Code Tokens Actually Go: Cache Writes, Plugins and the Instructions Nobody Reads

A week of my own Claude Code usage, 2.5 billion tokens, broken down by what it would cost at list price. Output was under half a percent of the tokens; cache writes were nearly half the bill. What that means for terse-reply tricks, plugins installed on hype, and the instruction files that load into every session.

Where Claude Code Tokens Actually Go: Cache Writes, Plugins and the Instructions Nobody Reads

Every few weeks a new trick for "saving tokens" in Claude Code goes around: a skill that makes the model answer like a caveman, a prompt that bans pleasantries, a plugin that promises to make the agent smarter and cheaper at once. I tried a few, and then did the boring thing first: I counted where my tokens actually went.

The answer changed what I optimise. The model's replies, the thing all those tricks shorten, were the smallest line on the bill. The big lines were things that load before I type anything.

Summary

  • Count before you optimise. In my week, output was 0.44% of tokens and about 17% of the cost at list price. Cache writes were about 47%.
  • Terse-reply tricks save little. Even halving every output token would save at most 8%, and the words they drop are often the reasoning you need.
  • The prefix is the bill. Instruction files, the memory index, skill descriptions, MCP server instructions and plugin hooks load into every session. Read /context in a fresh session.
  • Install skills, not bundles. Keep the skills you use (licence permitting), read a plugin's hooks before enabling it, and scope plugins to the project that needs them.
  • Keep instruction files to one line per rule. Put the long form in a file the line names, not one it imports.
  • Give anything that loads automatically a ceiling, enforced by a test.
  • Keep the cache warm. Delegate wide searches to subagents, /clear between unrelated tasks, and switch models only at task boundaries.

A week of tokens, counted

Claude Code writes every session to JSONL files under ~/.claude/projects/, and each assistant message carries a usage block. Summing a week of them is a dozen lines of Python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import glob, json, os, time

totals = dict(input=0, cache_read=0, cache_write=0, output=0)
week = time.time() - 7 * 86400
for path in glob.glob(os.path.expanduser("~/.claude/projects/*/*.jsonl")):
    if os.path.getmtime(path) < week:
        continue
    for line in open(path, errors="ignore"):
        try:
            msg = json.loads(line).get("message")
        except ValueError:
            continue
        usage = msg.get("usage") if isinstance(msg, dict) else None
        if not usage:
            continue
        totals["input"] += usage.get("input_tokens", 0)
        totals["cache_read"] += usage.get("cache_read_input_tokens", 0)
        totals["cache_write"] += usage.get("cache_creation_input_tokens", 0)
        totals["output"] += usage.get("output_tokens", 0)
print(totals)

My week, across 879 session files:

KindTokensShare of tokens
Cache reads2,362 M96.4%
Cache writes77 M3.2%
Output10.9 M0.44%
Uncached input0.03 M~0%

Token counts mislead, because the four kinds are priced very differently. At the list prices on Anthropic's pricing page today for Claude Opus 5.5 ($4 per million input tokens, $8 for a one-hour cache write, $0.20 for a cache read, $20 for output), the same week looks like this:

KindShare of cost
Cache writes~47%
Cache reads~36%
Output~17%

That is an approximation: not every session ran on that model, and a subscription does not bill per token at all. It still meters usage, though, and the proportions are what matter here.

Why terse-reply tricks barely move the bill

Output was about a sixth of the cost. And a terse-reply style only shrinks part of that sixth: the prose. Tool calls, code, diffs and file writes are output too, and no style instruction makes a diff shorter.

Suppose a trick halved every output token, prose and code alike, with no loss in quality. That is an 8% saving, at best. In practice it is a fraction of that, and it comes with a cost that is harder to see: a model told to drop words also drops the sentence that explained why it chose an approach, which is the sentence I needed when reviewing its work.

I keep replies short because short replies are easier to read. I stopped treating it as a cost lever.

The real bill: what loads before you type

Every turn re-sends the whole conversation, from the system prompt down. Prompt caching is what makes this affordable: the unchanged prefix is read back from cache at a twentieth of the input price. A cache write happens when a prefix is new: the start of every session, after a compaction, after the cache expires on an idle session, after switching models, or when something early in the prompt changes.

So the size of the always-loaded prefix is multiplied twice. Once as a cache write each time a session starts or the cache is rebuilt, and once as a cache read on every single turn after that. In my numbers, those two lines were 83% of the cost.

What sits in that prefix, before your first message:

  • the harness's own system prompt and tool definitions;
  • every CLAUDE.md or AGENTS.md in scope, including anything they @-import (an import is inlined, not deferred);
  • the auto memory index, MEMORY.md, up to its first 200 lines or 25 KB;
  • one line per installed skill: its name and description;
  • the instructions each connected MCP server sends;
  • whatever plugin hooks inject at SessionStart.

The /context command in Claude Code shows this breakdown for the current session. Run it in a fresh session in your main project and read it before optimising anything else. Mine had grown by accretion: every one of those items had been added for a good reason, once, and nobody had ever subtracted.

Plugins installed on hype

This is where I found the most waste, and it was not only tokens.

A plugin is a bundle: skills, slash commands, hooks, sometimes MCP servers. You install it for one skill you read about in a thread and you get all of it, in every session, in every project, whether that session needs it or not.

The token cost of a skill is modest. Only its description sits in context until the skill is invoked; the body loads on demand. Fifteen skill descriptions are a few hundred tokens. MCP servers used to be worse, with every tool schema in context; current Claude Code defers most tool schemas behind a search, but a server's instructions still load into every session.

The SessionStart hook is the one to read carefully. A hook can print text and Claude Code adds it to the context. One popular skills plugin I used injected a roughly 3 KB document into every session telling the model that if there was "even a 1% chance" a skill applied, it had no choice but to invoke it. That is around 800 tokens per session, which is not much. The bigger cost was behavioural: a standing instruction, above my own project instructions in priority as far as the model could tell, nudging it toward loading process skills for one-line questions. My project already had its own workflow; the plugin was arguing with it on every turn.

None of this makes the plugin bad. Several of its skills were genuinely good and I still use five of them. What I changed was the unit of installation:

  • Keep the skills you use, not the plugin. If the licence allows it (this one is MIT), copy the skill folders you actually invoke into your own dotfiles, keep the licence file next to them, and record the upstream commit you copied from so you can diff against new releases. Then disable the plugin.
  • Audit hooks before enabling a plugin. Read its hooks configuration. A hook that runs on every session start or every tool call is code and context you are opting into permanently.
  • Scope plugins to the project that needs them. A browser automation plugin belongs in the settings of the repo that does browser testing, not in the global settings that every session reads. The same goes for language servers for a language you touch once a month. On a laptop this also saves real memory: each Claude Code process starts the MCP servers it is configured with.
  • Be suspicious of overlap. Two plugins that both claim to "plan before coding" will both put their descriptions in context and both compete for the same turn.

Instruction files that stay short

My main AGENTS.md had grown to about 39 KB. Every rule in it was real, and most rules carried their history: the incident that caused them, the date, the decision record. That history is valuable when a change touches the rule, and pure overhead on the other 95% of turns.

The fix that held up:

  • One line per rule in the file that always loads. State the rule, not the story.
  • The long form lives in a file the line names. The model reads it when the work touches that rule. I moved the full text to a RULES.md that nothing imports; an @ import would have put it straight back into every session.
  • Subdirectory instruction files for subdirectory rules. Claude Code loads a CLAUDE.md in a subdirectory only when it works in that directory.

The file went from 39 KB to 13 KB. That cut about 6,500 tokens from the prefix of every session in that project, and I have not seen the agent miss a rule since. If anything, adherence improved: a short file of rules reads like rules, while a long file reads like background.

Memory that cannot grow forever

Claude Code's auto memory keeps an index, MEMORY.md, plus one topic file per fact. Only the index loads at session start, and only its first 200 lines or 25 KB; topic files are read on demand. The autoMemoryDirectory setting can point it at a directory inside your repo, so the memory is versioned and shared between machines.

The 200-line limit fails silently. Past it, the tail of the index is simply not loaded, and the agent forgets that those memories exist. Mine had reached 187 lines before I noticed.

So I added a test that fails well before the limit, at 160 lines or 20 KB, and whose failure message tells the agent what to do: merge memories on one topic into one file, delete stale ones, shorten index lines. A second assertion checks that every memory file is linked from the index, so a merge cannot orphan a file. Consolidating took the index from 187 lines to 113, and the test keeps it there without me thinking about it.

The general point: anything that loads automatically needs a ceiling, enforced by something other than good intentions.

Habits that keep the cache warm

A few things that cost nothing:

  • Delegate wide searches to a subagent. A search that reads thirty files puts thirty files of output into your main context, and you pay to re-read it on every later turn. A subagent reads them in its own context and hands back a paragraph. Give it a narrow tool set, so it does not carry every MCP server you have connected.
  • Clear between unrelated tasks. A long session about task A makes every turn of task B pay for A's history. /clear costs one cache write; carrying the history costs a cache read of all of it, every turn.
  • Do not edit the always-loaded files mid-session expecting them to take effect. Start a fresh session after you change them, once, deliberately.
  • Switch models at task boundaries, not mid-task. The cache is per model, so a switch rebuilds it.

A short checklist

  1. Count your own tokens by kind, and price them. Optimise the biggest line.
  2. Run /context in a fresh session and read what loads before you type.
  3. For every plugin: which of its skills did you invoke this month? Read its hooks. Keep the skills, drop the bundle, or scope it to one project.
  4. Cut the always-loaded instruction file to one line per rule; move the long form to a file it names, not one it imports.
  5. Put a ceiling, with a test, on anything that grows and loads automatically.

None of this made the agent less capable. The sessions got cheaper, and the instructions the model actually sees got shorter and more consistent, which is the part I care about more.

This post is licensed under CC BY 4.0 by the author.