...the longer they run
Every message in a Claude Code session sends the whole conversation back to the model. Prompt caching makes that re-read cheap per token, but cheap isn't free. And the longer the session runs, the more there is to re-read.
I checked one of my own sessions (236 requests, Opus 5.5). By the last quarter, each request was carrying about 3.9 times the tokens of a request in the first quarter.
I put together a short Python script so you can see the same curve in your own sessions. We'll get to that, along with a few habits that might help flatten it.
What happens on every turn
The model doesn't remember anything between requests. Claude Code has to send the system prompt, your CLAUDE.md, the conversation so far, all the tool results and whatever you just asked it. Every time.
And every tool call Claude makes is another request, carrying all that history plus whatever the tool returned.
Anthropic's cost page says it plainly: "a one-line question in a session that has been open all day still draws usage for the whole conversation."
Now, before anybody points out that caching exists, yes. Thank goodness it does.
The API matches the beginning of each request against what it processed recently and bills that prefix at the cached rate. On Opus 5.5 and Sonnet 5.5, that's 0.05x the base input price. On most other models, it's 0.1x.
That's a pretty good discount. Trouble is, the discount applies to a growing pile of tokens.
Someone on r/ClaudeCode described it as "a session that's twice as long costs roughly 4x".
Close enough. If the context grows steadily, every request is bigger than the one before it. Add up all those increasingly large requests and the total grows with the square of the length.
You can have a perfectly reasonable cost per token and still end up with an unreasonable number of tokens.
Measure your own
Claude Code stores its sessions as JSONL files under ~/.claude/projects/.
Each assistant entry includes the API's usage block, with cache_read_input_tokens, cache_creation_input_tokens and input_tokens. Add those together and you've got the tokens sent with that request.
One caution: the log format isn't documented. This is a way to inspect what's happening, not an API you should build your retirement plans around.
Here's the script:
import glob, json, os, sys
from datetime import datetime
path = sys.argv[1] if len(sys.argv) > 1 else max(
glob.glob(os.path.expanduser("~/.claude/projects/*/*.jsonl")), key=os.path.getmtime)
reqs, seen = [], set()
for line in open(path, encoding="utf-8"):
try:
e = json.loads(line)
except ValueError:
continue
m = e.get("message") or {}
u = m.get("usage")
if e.get("type") != "assistant" or not u or m.get("id") in seen:
continue
seen.add(m.get("id"))
t = datetime.fromisoformat(e["timestamp"].replace("Z", "+00:00"))
read = u.get("cache_read_input_tokens", 0)
write = u.get("cache_creation_input_tokens", 0)
fresh = u.get("input_tokens", 0)
reqs.append((t, read, write, fresh))
if not reqs:
sys.exit("no requests with usage in " + path)
total = sum(r + w + f for _, r, w, f in reqs)
n = len(reqs)
print(f"{n} requests, {total:,} input tokens sent (cache read + cache write + uncached)")
q = max(n // 4, 1)
for i, name in enumerate(["first", "second", "third", "last"]):
part = reqs[i * q:(i + 1) * q] if i < 3 else reqs[3 * q:]
if not part:
continue
s = sum(r + w + f for _, r, w, f in part)
print(f" {name} quarter: {len(part):4} requests, avg {s // len(part):>8,} tokens each, "
f"{100 * s / total:5.1f}% of the session")
print("cache rebuilds over 20,000 tokens after a gap of 5+ minutes:")
for prev, cur in zip(reqs, reqs[1:]):
gap = (cur[0] - prev[0]).total_seconds() / 60
if cur[2] > 20000 and gap >= 5:
print(f" {cur[0]:%H:%M} after {gap:5.0f} min idle: {cur[2]:,} tokens re-written")
Run it with:
python sessioncurve.py # your most recent session
python sessioncurve.py path/to/session.jsonl # or a specific one
And here's what it reported for my session (October 2 to 5, Claude Code 2.1.286, Opus 5.5, never compacted; times are UTC):
236 requests, 88,434,005 input tokens sent (cache read + cache write + uncached)
first quarter: 59 requests, avg 152,605 tokens each, 10.2% of the session
second quarter: 59 requests, avg 284,829 tokens each, 19.0% of the session
third quarter: 59 requests, avg 467,677 tokens each, 31.2% of the session
last quarter: 59 requests, avg 593,768 tokens each, 39.6% of the session
cache rebuilds over 20,000 tokens after a gap of 5+ minutes:
13:51 after 173 min idle: 127,395 tokens re-written
17:04 after 109 min idle: 150,117 tokens re-written
18:15 after 70 min idle: 159,152 tokens re-written
20:20 after 108 min idle: 177,133 tokens re-written
... eight more, the biggest 533,486 tokens after an overnight gap
The last 59 requests used almost four times what the first 59 did.
But there's another interesting bit in those numbers.
Twelve times during that session, I wandered off for over an hour. Meals, meetings, sleeping. You know, the occasional interruption to my otherwise fascinating life as a guy staring at code.
Each time I came back, the first message had to write the whole context into the cache again.
And the later in the session that happened, the more expensive that rebuild was.
Breaks cost more than you'd think
A cache write costs considerably more than a cache read.
On a Claude subscription, within plan usage, Claude Code requests a one-hour cache for the main conversation. Writing to that cache costs 2x the base input price.
On an API key or usage credits, the default cache is five minutes, and writes cost 1.25x.
Once the cache expires, your next request has to rebuild it. And that means paying the write rate on the context you've accumulated.
Let's do the math for Opus 5.5.
A one-hour cache write costs 2x base input. A cached read costs 0.05x.
That's 40 to 1.
Look at the rebuild at 20:20 in my output. I'd been away for 108 minutes, and the context had grown to 177,133 tokens.
Rebuilding that cache cost about what 40 ordinary cached turns of that size would have.
Lunch is expensive. Apparently even when you're not buying any.
Most articles about prompt caching say it lasts five minutes. That's the API default. On a subscription, the main conversation generally gets an hour.
You can check which one you're using:
claude -p "hello" --output-format json
Look under usage.cache_creation.
One-hour writes appear as ephemeral_1h_input_tokens. Five-minute writes appear as ephemeral_5m_input_tokens.
If you're using an API key, the promptCacheTtl setting can request 1h. Might be worth considering if you step away frequently, although the longer cache costs more for short bursts that never sit idle.
What flattens the curve
Use /clear between unrelated tasks.
Anthropic's cost page calls it free. When you clear, the next task starts with the system prompt instead of everything you've been doing since breakfast.
If you might want to return to the old session, run /rename first. You can /resume it later.
Compact before a break, not after.
/compact has to read the conversation to summarize it.
If the cache is warm, that read comes from the cache. If you've been away long enough for it to expire, you're paying for an uncached read of the history.
So if you're about to walk away, that's a good time to compact. Not when you get back.
In fact, /usage will suggest exactly that when it applies: "Compact before you step away".
Try to pick your model at the beginning.
Each model has its own cache. Switch models with /model halfway through a task and the new model has to read the history uncached.
Sometimes switching is worth it. Just be aware that it isn't free.
Check what's happening when you're not typing.
A /loop or another session-scoped scheduled task can keep sending requests while you're doing something else.
Same goes for goal check-ins and messages arriving from your other sessions. Each one sends the full context.
Your session can be spending tokens while you're off making a sandwich.
Watch what gets added to the conversation in the first place.
This one is easy to overlook.
Suppose the agent opens a 900-line source file at request 20 because it needs to inspect one function.
That file is now part of the conversation. At request 200, it's still being re-read unless you've cleared or compacted.
Same problem with enormous test outputs and raw logs.
A hook that trims test output might help. So might a subagent that reads a log and returns a summary instead of dumping the whole thing into the main conversation.
And for code, retrieving the one function instead of opening the entire file keeps the 900 lines out of the history to begin with.
That last problem is why I built jCodeMunch.
Install it with pip install jcodemunch-mcp, run jcodemunch-mcp init, and have your agent look up code by symbol instead of reading whole files.
On our pinned benchmark (15 lookups across express, fastapi and gin, counted with tiktoken cl100k_base), one lookup costs 96.2% fewer tokens than a grep-and-read agent. That's per lookup, not per session.
The method and reproduction steps are on jcodemunch.com. Run it yourself if you're skeptical. I would.
One other thing worth knowing: on Claude Code 2.1.251 or later, /usage includes a "Prompt cache (main)" line.
It shows how much input came from cache, how many misses you had and whether the cache is warm right now.
Might be worth checking before you go get that coffee.
FAQ
If cached reads are 0.05x, why care?
Because 0.05x of 590,000 tokens, 59 times over, is still a lot of tokens. And on a subscription, they all count toward the same five-hour and weekly limits.
A discount doesn't make the quantity disappear.
Does /compact save tokens?
On later requests, yes. Instead of carrying the full history, Claude carries a shorter summary.
But the compaction itself is a big request. It's cheaper when the cache is warm and full price when it's cold.
Why did my usage jump after lunch?
Probably because the cache expired while you were away. Your first message back had to rewrite the context at the write rate.
The script's rebuild lines should show you when that happened.
Will a code index fix my session cost?
It shrinks what each code lookup adds to the conversation, which keeps the context smaller. The turn count, the breaks and the tool output that isn't code are still yours to manage, with the habits above.
Run the script against your longest session from last week. You might be surprised where the line starts bending...

Top comments (0)