Who's Eating Up Claude Code's Context? Measuring Tool Call Returns

Use a script to tally tokens returned by tools in Claude Code session logs, find the real quota hog, then optimize.
The article notes that Claude Code re-sends the full conversation history with every request, so large text returned by tools keeps consuming quota until cleared or compacted. It recommends first running the zero-cost `/context` and `/usage` commands, then writing a local script to parse JSONL logs under `~/.claude/projects/`, joining `tool_use` with `tool_result` and counting tokens by tool name. The results usually show the culprit is Bash test logs, verbose MCP server output, or full-file reads by Read. Using CPython's `decoder.py` as an example, reading just one function can save over 5x the tokens. Fixes go from lowest to highest cost: ask narrower questions, run `/clear` when switching tasks, install code intelligence or symbol-indexing tools. Always re-run the script to verify the gains.
If you feel like Claude Code is burning through your quota faster than expected, don't rush to change how you use it. The thing actually bloating your session often isn't the tool category you'd suspect. Claude Code stores every session as a local JSONL log — write a small script to tally the tokens returned by each tool, and the answer is usually surprising.

Why You Should Measure Tool Call Returns
Claude Code's billing mechanism dictates this: every request sends the entire conversation history, and the result the model gets back after each tool call gets re-sent as part of the next round of requests. This comes from Anthropic's official cost documentation — it's not a guess.
The consequence: once a Read pulls in a big file, that file's contents stay in the session, and every subsequent request carries it along — until you run /clear or context compaction kicks in. Caching makes repeated reads cheaper, but not free; under a subscription plan, those tokens still eat into your usage window.
So to see clearly "who's hogging the space," tool return results are the most important thing to measure.
On context windows and automatic compaction: The large models Claude Code relies on (such as the Claude series) have a fixed context window limit, usually 200K tokens. When the accumulated conversation history approaches this cap, the client automatically triggers compaction — summarizing earlier content into a shorter summary before continuing, rather than discarding it outright. Compaction itself consumes a model call, and the summary can't preserve every detail, so frequent compaction not only adds overhead but may also cause the model to lose important early information. This is why proactively running /clear between unrelated tasks is cheaper than passively waiting for compaction — it zeroes out the usage completely, at the cost of only losing the current session's short-term memory. Understanding this is what makes it clear why an early large file read "keeps charging you" until compaction happens.
First Look at the Built-in Views — Zero Cost
Before writing any script, there are two free commands worth running first:
/context shows what's currently taking up space in your context window.
/usage — under Pro or Max subscriptions — breaks down recent usage by skill, subagent, plugin, and individual MCP server, and flags any item exceeding 10% of recent usage, such as long context or cache misses.
Both commands have an obvious blind spot: they don't tell you which specific tool calls or which files stuffed the text in. That information is buried in the logs.
Session Logs: The JSONL Format
Claude Code saves each session as a JSONL file under ~/.claude/projects/. A tool call is a tool_use block with an id and name, and its result comes back on a later line as a tool_result block carrying the same id. Join the two, count the tokens in each result, and group by tool name.
The script logic is roughly: encode the result text with tiktoken, iterate over each line of JSON, match tool_use with tool_result, accumulate into a counter keyed by tool name, and separately record file-path dimensions for the Read tool. It reads a local file and prints a table — all computation happens on your machine. Since these logs contain your prompts and source code, keeping the data local has real value.
How to run it:
pip install tiktoken
python toolcost.py # most recent session
python toolcost.py path/to/session.jsonl # specify a session
Two Honest Limitations
cl100k_base is not Claude's tokenizer, so use the results to rank relative sizes, not as billing numbers.
Each result is counted once at the size it arrived at, and doesn't model how many subsequent requests carried it. This means a big read early in a long session actually costs more than an equally sized read near the end.
Also, the log format isn't publicly documented and changes between versions. If the script prints an empty table, open the file and look at a few lines of the actual JSON structure.
What JSONL is: JSONL (JSON Lines) is a text format where each line is an independent JSON object, often with a .jsonl extension. Compared to a single large JSON file containing an array, it's better suited to streaming append writes — each event gets written as one line the moment it occurs, with no need to read the whole file and rewrite it. Claude Code records sessions exactly this way: user messages, assistant replies, tool calls, and tool results each occupy a line, and lines are linked via id fields. That's also why the script can iterate line by line, counting as it reads. Note that logs may include interleaved records from multiple conversation turns, plus some non-conversational event lines; when writing the parser, skip lines missing expected fields to avoid errors from format changes.
cl100k_base and tokenization differences: tiktoken is OpenAI's open-source tokenizer library, and cl100k_base is one of the encodings it uses for the GPT-3.5/4 series. Anthropic's Claude uses its own tokenizer, and the two don't produce the same token count for the same text — typically differing by single digits to low teens of percentage points, and potentially more for code and special symbols. So absolute values from cl100k_base don't map directly to your Claude bill, but since its tokenization is stable across text types, it remains reliable for comparing "which tool returns more." The "5x" and "95%" ratios mentioned in this article are all relative comparisons based on the same tokenizer, and are barely affected by this deviation.
How to Read the Table
Is Bash at the top? Usually that's test output or build logs. Filter it out before the model ever sees it (Anthropic's cost page offers a hook recipe that keeps only failure lines), or hand noisy tasks to a subagent and let only a summary come back.
Is an MCP tool at the top? That means the server habitually returns large blocks of text. See if you can ask it to return less; if not, consider whether it needs to be running all day.
Is Read at the top, with a list of source file paths beneath it? That means the agent is reading whole files just to look at a part of them. This is exactly the scenario we'll unpack next.
Full File Reads vs the Part You Actually Need
Take an example you can verify with standard Python: in CPython 3.12.4, the full json/decoder.py is 3,024 tokens (tiktoken cl100k_base), while a single function within it, py_scanstring, is just 557 tokens.
Using the ast module to locate the function and extract the source segment, comparing the two encoded lengths, the result is: the agent needed only that function but read the entire file, ingesting more than 5x what was actually required. And that's just a 356-line file. The larger the file, the wider the gap; small files show almost no difference.
Fixes, From Lowest to Highest Cost
Ask narrower questions. "Improve this codebase" makes any agent scan broadly. Specify file names and function names — it costs you ten extra seconds of typing.
Run /clear when switching to unrelated work. Everything still sitting in the session gets re-sent.
If you're using a language with a type system, install the corresponding code intelligence plugin. Anthropic's docs point out that a single go-to-definition call can replace the "grep around, then read a few candidate files" flow. It's a first-party capability, free.
Or use a symbol index. jCodeMunch is one such tool: it indexes a repository once, then hands individual functions or classes to the agent by byte offset, replacing full-file reads. Installation is just two commands, and it needs no API key at runtime:
pip install jcodemunch-mcp
jcodemunch-mcp init
Its author's published benchmark shows an average 95% reduction in code-reading tokens across 3 real repositories and 15 tasks (measured with tiktoken cl100k_base). The 99.8% figure on the homepage is the result of a single query on fastapi/fastapi: reading the entire source tree takes 214,312 tokens, while going through the index takes only about 480. But no agent would read the entire tree, so that number should be treated as a theoretical upper bound.
Whichever option you choose, re-run toolcost.py on a session doing the same kind of work. The index tool's own returns will also show up in the table and count toward the cost. If the drop in the Read line doesn't exceed the added lines, it's not helping — pull it out.
What symbol indexing and MCP are: MCP (Model Context Protocol) is an open protocol from Anthropic that lets clients like Claude Code plug into external tools and data sources in a unified way — an MCP server is like a capability plugin mounted onto the agent. Symbol-indexing tools (like the jCodeMunch mentioned here) are one concrete application of MCP: they pre-scan the codebase and build a mapping from symbols like functions and classes to file byte offsets. When the agent needs to see a function, the tool returns the source segment for that symbol directly, instead of stuffing the whole file into context. Compared to the traditional "grep to locate, then read candidate files, then filter" flow, symbol indexing compresses multiple round trips into a single precise fetch, significantly reducing the total tokens entering the session. These tools' returns also get carried by subsequent requests, so the benefit should be evaluated after subtracting their own overhead.
A Few Common Questions
Does fewer tokens mean a smaller bill? If you use an API key, input spending roughly drops in step. Under a Pro or Max subscription, the price stays the same — what changes is how long you can go before hitting the limit.
Is tiktoken accurate enough for Claude? For comparing the relative size of different tools within a session, yes; for precise counting, no.
Can this method work on Cursor or Codex? The script only reads Claude Code logs. The method itself applies to any agent that logs tool results, but their formats haven't been verified.
What if all my files are small? Then file reads probably aren't your problem, and indexing won't help much. Look at the other rows in the table.
Measure First, Then Decide
The core of this whole article is really just one sentence: don't judge by feel who's eating your quota — the logs are right there on your hard drive, run a script and you'll have the answer. The tool's own author admits he has a stake (he wrote jCodeMunch), but precisely because of that, he repeatedly stresses "measure first, don't take my word or anyone else's."
Before switching tools, use data to confirm where the problem actually lies. If patterns show up in your table that this article doesn't cover, that's what's genuinely worth discussing.
One-line Summary
Use a script to tally tokens returned by tools in Claude Code session logs, find the real quota hog, then optimize.
Paragraph Summary
The article points out that Claude Code re-sends the full conversation history with every request, and large blocks of text returned by tools keep consuming quota until cleared or compacted. The author suggests first running the zero-cost /context and /usage commands, then writing a local script to parse the JSONL logs under ~/.claude/projects/, joining tool_use with tool_result and counting tokens by tool name. The results usually show the culprit is Bash test logs, verbose text returned by MCP servers, or full-file reads by Read. The article uses CPython's decoder.py as an example, showing that reading just one function can save over 5x the tokens. Fixes go from lowest to highest cost: ask narrower questions, run /clear when switching tasks, install code intelligence or symbol-indexing tools. After optimizing, you must re-run the script to verify — if the indexing tool's own overhead cancels out the gains, remove it.
Related articles

Uber Deploys ChatGPT Work Company-Wide: AI in the Workplace Enters the Scaled Implementation Phase
Uber deploys ChatGPT Work to 30,000+ employees, extending it from engineering to all roles. We analyze its expert-engineer pairing, end-to-end automation, and questions of AI control and power concentration—insights for scaling AI in the workplace.

Shift at Midnight Hands-On: When "ID Verification" Becomes Alien Hunting
Shift at Midnight blends ID verification with horror survival in a four-player co-op game. We break down its inspection mechanics and chaotic humor.

Zelensky Recounts the Outbreak of War: I Need Ammunition, Not a Ride
Zelensky recalls the first day of war in a YouTube interview: farewell to family, rejecting Biden's evacuation offer with "I need ammunition, not a ride," and how Bucha shaped his stance on compromise and peace talks.