DEV Community

Cover image for I measured 1,436 Claude Code turns. Here's what the terminal never shows you.
duqa
duqa

Posted on

I measured 1,436 Claude Code turns. Here's what the terminal never shows you.

A few weeks ago I spent an afternoon on a feature with Claude Code. Hours, a lot of tokens, a few subagents running. When I asked what it was doing I got a summary back, and nothing I could check it against. The result was bad enough that I threw the branch away, and I still couldn't tell you where it went wrong or which agent took it there.

That bothered me more than the wasted afternoon. So I went looking for the logs, and it turns out Claude Code writes down everything it does.

Where it all is

Every session is a JSONL file under ~/.claude/projects/<project>/<session-id>.jsonl. The folder name is your working directory with the slashes replaced by dashes. One JSON object per line, appended
as the session runs.

ls -t ~/.claude/projects/*/*.jsonl | head -5
Enter fullscreen mode Exit fullscreen mode

Each assistant line carries a usage block with input_tokens, output_tokens, cache_creation_input_tokens and cache_read_input_tokens. That is the entire cost of your session,
itemised, sitting on your disk.

I wrote a parser and pointed it at everything I had. Here is what surprised me.

1. A turn is not one API call. It's usually seven.

I had a mental model where I send a prompt and Claude answers. What actually happens is a loop: it reads a file, greps, edits, reads again, and each of those steps is a separate API call that re-sends
your entire context.

Measured over 1,436 turns in 60 sessions, counting distinct requestId values between one user prompt and the next:

calls in one turn
median 7
p75 17
p90 36
p99 112
max 235

A quarter of turns are three calls or fewer. About one in six goes past two dozen. So "why did that simple question cost so much" usually has a boring answer: it wasn't one question, it was thirty-six round trips.

Worth knowing: the file gets one line per content block, not per API call. A single response becomes a thinking line, then a text line, then one line per tool_use — all sharing a requestId, and all carrying the same usage block. If you sum usage per line you will multiply your own cost by three or four. Group by requestId first.

2. A session is not one context window

This is the one that changed how I read a session.

Every subagent gets its own context window, on whatever model it was handed — which is not necessarily the model you are talking to. And the parent transcript doesn't contain their work. It records that a subagent was spawned, and the text that came back. That's it.

The children write their own separate files. So when you look at "the session", you are reading a summary of documents you have never opened, and the tokens you paid for are mostly in those.

If you fan out five subagents, the thing on your screen is the least informative file involved.

3. Sessions die silently, and the obvious check is wrong

When an API call fails mid-turn — an expired login, a rate limit, an overloaded server — the turn stops. The terminal looks exactly like it looks when Claude is thinking. No error banner, no sound, no change in the spinner. The only way to find out is to come back later and scroll up.

Those failures are in the log, and they're easy to find:

grep -l '"isApiErrorMessage":true' ~/.claude/projects/*/*.jsonl
Enter fullscreen mode Exit fullscreen mode

They're type: "assistant" lines with an error field, and content along the lines of "API Error: Connection closed mid-response. The response above may be incomplete."

Here's the part that caught me out. My first instinct was to check whether the error was the last line of the file — if nothing came after it, the session died there. That test finds nothing, and it's wrong. Claude Code keeps appending after the session is over: last-prompt, system, file-history-snapshot, mode changes. The file grows even when the conversation is finished.

The real question is whether another assistant reply ever follows the error. In my own logs, of the seven API errors I could find, three were never followed by another answer. Those sessions ended right there, and at the time I had no idea.

One in two is my corpus, not a law — run it on yours, the interesting number is your own.

4. You pay for the same context, over and over

A stateless API means every call re-sends the whole conversation. Prompt caching softens that: the repeated part bills as cache_read_input_tokens, which is far cheaper than fresh input.

Until the cache is cold. Then the whole prompt gets re-created at full price as cache_creation_input_tokens, and you pay to rebuild something you already had.

I added it up across my own logs — 60 sessions, 21,810 distinct API calls — and the shape surprised me more than I expected:

tokens share
cache_read 6,913,637,348 98.9%
cache_creation 63,949,848 0.9%
output 16,336,642 0.2%
fresh input 102,777 0.0%

98.9% of everything crossing the wire is context being read back. The new text I typed rounds to zero. The output — all the code and prose Claude actually produced over those sessions — is 0.2%.

One thing this is not: a bill. Cache reads cost a fraction of fresh tokens, so the money does not split along those lines at all. It's a shape, and the shape is that an agent session is mostly the same context moving back and forth, with the new work as a rounding error on top.

One warning if you go counting yourself: each line carries the usage of its whole API call, repeated. A single response becomes a thinking line, a text line and one line per tool call, and all of them report the same numbers. Sum per line and you'll multiply your own cost by three or four.
Group by requestId first.

5. Some things never reach the log at all

One blind spot worth knowing about, because it limits what any log-reading tool can tell you: the approval dialog. When Claude asks permission to run something, nothing is written until you answer. A session sitting there waiting for you looks, on disk, exactly like one that has finished.

So "the logs contain everything" isn't quite true. They contain everything that happened — and a session waiting on a human hasn't happened yet.

The part where I admit what this became

I did all this reading with scripts, and then I got tired of running scripts, so I wrote a thing that draws it live while a turn happens: the window filling, every call with its latency and tokens, each
subagent folded under the spawn that launched it on its own window and model. It's called seedeep, it's MIT, and it reads the logs without touching them: https://github.com/duqaXxX/seedeep

Seedeep

But the point of this post is the logs, not the tool. Everything above is in files you already have, and a hundred lines of Python gets you most of it. The numbers surprised me enough that I think they are worth knowing whether or not you ever run anything I wrote.

Top comments (0)