The Token Tax
What Claude Code carries before you type anything — measured, cut, and
verified with /context.
Claude Code shipped a 28,000-token baseline before I typed anything — 2.4k of
system prompt and 25.6k of tool schemas, re-sent on every message. Putting bare
tool names in permissions.deny and disabling unused feature flags cut it to
5,600 tokens, a reduction of 22,400 per turn. Both figures come from Claude Code's
own /context command on the same project with the same eight-message conversation,
with only the tool schemas changed between captures.
The observation
Every message you send Claude Code carries a payload you never see: tool schemas, a skills catalogue, instructions for features you may never touch. All of it ships with every request, and you pay for it every turn.
The number that started this was 28,000 tokens — the size of my context window at message zero, before I had typed a single word. Most of it turned out to be removable.
How a Claude Code request is assembled
Claude Code is a harness: a loop that assembles a full API request, sends it, executes any tool calls the model asks for, appends the results, and sends everything again. The model is stateless, so the system prompt and every tool definition are re-sent on each round trip.
| Part of the request | What it holds | Size |
|---|---|---|
| System prompt | Identity, rules, tone, environment info, feature instructions | ~2–3k |
| Tool definitions | Full JSON schema for every enabled tool — the biggest block | ~3–26k |
| Skills catalogue | Descriptions of every bundled and custom skill | cuttable |
| Memory files | CLAUDE.md, global and project, verbatim | ~0.2–2k |
| MCP tool schemas | Every registered server, whether you use it or not | cuttable |
| Conversation history | All prior messages plus every tool result | grows |
| Your message | The only part you typed | tiny |
This costs you twice. Money: input tokens are billed per request, and caching softens that without erasing it. Attention: everything the model reads past is noise sitting between it and your problem. Trimming irrelevant context usually improves output quality too.
Measuring it with /context
Before changing anything, run /context in a fresh session and write the numbers down.
This is the baseline everything else is measured against.
Before28.0k / 1.0M
After5.6k / 1.0M
The system prompt stayed at 2.4k — settings barely touch it. Everything that moved was tool schemas.
Why does Claude Code use so many tokens before you type?
Tool definitions are the biggest block because a schema ships whether or not the tool is ever called: the model needs the definition in context before it can decide to use it. Grouped by what they are for, here is what was sitting in mine.
Planning and interaction
- EnterPlanMode / ExitPlanModeToggles for plan mode, where the model drafts a plan for approval before touching code. Two schemas plus their instructions — wasted if you review diffs rather than plans.
- AskUserQuestionLets Claude pause and present structured multiple-choice questions mid-task. If you steer with plain follow-up messages, the interactive-prompt machinery is unused schema on every turn.
Niche editors and integrations
- NotebookEditEdits Jupyter notebook cells. Unless you work in notebooks, one of the larger single tools you will never call.
- DesignSyncPulls design context into coding tasks. ~2k+ of schema, irrelevant to any workflow that does not touch design files.
Background and remote machinery
- SendMessageMessaging between agents in multi-agent and background runs. Solo interactive coding never calls it.
- PushNotificationSends notifications about session events. You are sitting in the terminal.
- RemoteTriggerLets sessions be driven remotely. If your sessions start when you type, it does nothing.
- ReportFindings · ScheduleWakeupStructured reporting and wake-ups for background runs. Meaningless outside long-running background jobs.
- CronCreate / CronDelete / CronListRecurring scheduled tasks. Three schemas for a feature most people never enable.
How do you remove a tool from Claude Code’s context?
Put a bare tool name in permissions.deny and its whole definition drops out of
the payload. The model never sees it.
{
"permissions": {
"deny": ["NotebookEdit", "DesignSync", "PushNotification"]
}
}
"NotebookEdit" removes the tool from the payload. "Bash(rm *)" or
"Skill(dataviz)" blocks the matching call but leaves the full definition in the
request. To shrink tokens, use bare names. Scoped rules are for safety, not size.
Feature flags cut whole clusters
Some features bring a bundle of tools and instructions with them. A single top-level flag in
settings.json removes everything the feature carries. Use flags for the broad first pass,
then deny rules to pick off what is left.
| Flag | What it removes |
|---|---|
disableWorkflows | The multi-agent Workflow tool — typically the largest single line in the payload, ~5.3k of schema alone. The biggest single win. |
disableBundledSkills | All bundled skills from the catalogue Claude reads, while their slash commands stay typable. All-or-nothing; for per-skill control use skillOverrides |
disableRemoteControl | Machinery for controlling sessions from other devices |
disableClaudeAiConnectors | claude.ai connector integration reaching into your coding session |
disableArtifact | The Artifact tool, which publishes session output as a web page |
For partial skill control, skillOverrides takes "off" to remove a skill
completely, or "user-invocable-only" to keep its slash command typable while Claude no
longer reads its description on every message:
// settings.json
"skillOverrides": {
"dataviz": "off",
"review": "user-invocable-only"
}
Every MCP server you register loads its tool descriptions at session start, whether you call them
or not. For many people this is worth more than all the built-in trimming combined. Newer Claude Code
versions load MCP descriptions on demand once they would exceed roughly 10% of the window — so look
at /context before spending an evening pruning.
The 5.6k result
What remains is 2.4k of core system prompt — identity, safety, environment, which you cannot reduce — and 3.2k of core tools: the file, search and shell toolkit that makes Claude Code useful.
At 0.56% of a 1M window, the fixed-overhead problem is effectively solved. That is 22.4k tokens which used to ship on every message and now do not.
Which Claude Code tools should you keep?
Removing core tools saves a few hundred tokens and makes the agent substantially worse: it falls back to slower workarounds, or simply fails.
| Tool | Why it stays |
|---|---|
Bash | Cut it and Claude cannot run your tests, git, or build. The agent goes blind |
Read | Without it Claude cannot look at your code before changing it |
Edit | The precise way to change code; removing it forces slow full-file rewrites |
Write | Needed for new files; without it Claude can only patch what exists |
Glob | How Claude locates files fast; cut it and it falls back to slow shell hunting |
Grep | Finds where code lives without reading whole folders into context |
The goal of trimming is to remove noise, not to take away the tools that do the work. If you truly
never use them, WebSearch, WebFetch and TodoWrite can go too —
worth maybe another 1–2k.
"Deny Bash so it cannot break anything" removes the schema from context and makes Claude unable to work, while giving you no control over what it does with the tools it still has. Restricting what a tool may do is a different mechanism — see Guard.
Why do long Claude Code sessions get expensive?
Once the baseline is fixed, the cost curve moves to the chat itself. A model is stateless: each turn re-sends the entire growing history. Let a session grow to 1M context over roughly 100 turns and the total input lands near 50M tokens, not 1M.
| Model (input / output per 1M) | 1M billed once | Grown to 1M over 100 turns | Same, with caching |
|---|---|---|---|
| Claude Fable 5 · $10 / $50 | ~$14 | ~$525 | ~$60–75 |
| Claude Opus 5 · $5 / $25 | ~$7 | ~$262 | ~$30–38 |
| Claude Sonnet 5 · $2 / $10 | ~$3 | ~$105 | ~$12–16 |
Caching is what turns a $525 session into $60, but it only covers the part of the request that has
not changed. After the first message, your baseline is re-read at roughly a tenth of the price on
every turn. Editing settings, CLAUDE.md, or MCP config mid-session breaks the cache and
the next turn re-pays for the whole context at full price. Change settings between sessions, not
during.
Six habits that beat any settings change
- One task, one session. The cheapest context is the one you never build up. If you catch yourself saying "new topic, but while I have you here…", open a new session instead.
- Clear early, compact at milestones.
/clearwipes history between unrelated asks;/compactsummarizes it. Run it at a good stopping point — when tests pass, when a feature is done — not when the window is 90% full. - Write a handoff note. Before ending a long session, ask Claude to write decisions, the
file map and next steps into
NOTES.md. The next session reads a 1k summary instead of inheriting 400k of transcript. - Read only what you need. Every file Claude reads lives in context for the rest of the session. Point it at specific files and line ranges. Never paste a huge log into the chat — save it and ask Claude to search it.
- Let subagents take the mess. Everything a subagent reads disappears when it finishes; only its summary returns. Hand noisy research to one and the mess never enters your context.
- Protect the cache. See above — mid-session config edits are the expensive mistake.
One more that sits in the baseline rather than the habits: keep CLAUDE.md
short. Memory files are pasted into every single message, word for word — a 2,000-token
CLAUDE.md is a 2,000-token charge on every turn, all day. Keep only the short rules
Claude needs every time, and move long reference material into normal files in the repo, which Claude
can open when a task needs them and which cost nothing the rest of the time.
Capping what comes in
The deny list shrinks what ships at the start of every message. Most of the pile-up, though, happens during the chat: tool replies and command output land in the window and stay there for the rest of the session.
// settings.json — three caps that stop the flood at the door
"env": {
"MAX_MCP_OUTPUT_TOKENS": "8000", // default is 25,000 per reply
"BASH_MAX_OUTPUT_LENGTH": "30000", // truncate long command output
"MAX_THINKING_TOKENS": "10000" // thinking bills as output
}
A PreToolUse hook can go further and compress command output before Claude ever sees
it — or you can pipe through grep and tail yourself.
Compaction fires later than you think
Claude Code compresses your chat when it fills up. Left alone it does this late — around 84% — and on its own terms, with roughly 33k reserved that you never get. One test found compacting near 60% performed up to 39% better than waiting.
"env": { "CLAUDE_AUTOCOMPACT_PCT_OVERRIDE": "70" }
/compact also takes instructions — say what to keep and what to throw away rather than
accepting a generic summary. One catch: DISABLE_AUTO_COMPACT only turns off the big
auto-compact. Claude Code also clears old tool results quietly, with no message shown, and that keeps
running either way. Do not count on an old file read still being there; save findings to a notes file
as you go.
Quick questions do not deserve a session
A 50-token question asked at 300k context costs 300k+ input tokens, and its answer keeps costing on
every turn after. claude -p takes one prompt, answers, and exits — nothing stored,
nothing re-sent.
# ask, answer, done — nothing carried into any session
$ claude -p "what does ECONNREFUSED mean in a Node app?"
$ cat build.log | claude -p "why did this build fail?"
$ git diff | claude -p "review this change, list problems only"
Rule of thumb: if the answer will not change what you do in the current session, ask it with
-p in another terminal.
What 22.4k is worth
The cut is an 80% reduction in initial context overhead (28.0k → 5.6k). Claude Opus input lists at $5 per million tokens, so the arithmetic is one multiplication:
22,400 ÷ 1,000,000 × $5.00 = $0.112 per call
every 1,000 calls = 22.4M tokens · $112
Scale that to a month however your own usage actually looks. At 10 model calls per session, 30 sessions a day and 23 working days, it lands near $770 a month — but that is three assumptions stacked on one measurement, and your numbers will differ. The honest version of the claim is the one above the line: $0.112 per call, on a baseline you measured yourself.
Caching softens list prices, but the overhead still rides every request. Cut it once in
settings.json and you stop paying for it on every message that follows.
Three levers that only touch the bill
Model choice is the multiplier over everything above: send routine edits to Sonnet and save the Opus-tier models for architecture and hard multi-file bugs. Beyond that, three settings save money rather than context:
| Setting | Effect |
|---|---|
CLAUDE_CODE_DISABLE_1M_CONTEXT=true | Keeps you on the normal window if you do not need 1M |
DISABLE_NON_ESSENTIAL_MODEL_CALLS=1 | Skips small background calls for things like chat titles |
CLAUDE_CODE_MAX_OUTPUT_TOKENS | Caps how long a single answer can be — output is the expensive side |
Then run /cost at the end of a session to see what it actually spent. Change one
setting at a time and compare. Numbers beat guesses.
Rule of thumb: baseline is fixed cost, history is the running cost, model choice is the multiplier.
Apply and verify
None of this is a rulebook — it is a menu. If you live in plan mode, keep it. If you write
notebooks, keep NotebookEdit. Background jobs and multi-agent runs genuinely depend on
some of what looks like bloat. See your own payload first, then cut.
- Baseline it. Run
/contextin a fresh session. Write the numbers down. - Drop in the settings.
~/.claude/settings.jsonfor everything, or.claude/settings.jsonper project. Merge; do not clobber existing keys. - Restart the session. Settings load at session start. In Cursor, close and reopen the Claude panel or reload the window.
- Re-measure.
/contextagain. The delta is what you were shipping every turn and now are not. - Watch for breakage. Missing a feature? Remove its deny entry or flag, restart, done. Everything here is reversible.
Limits of this experiment
Everything above is one measurement of one setup, and it is worth being precise about what that does and does not establish.
- One configuration. Claude Code inside Cursor,
claude-opus-4-8on the 1M window, with the tools and MCP servers I happened to have enabled. Your 25.6k is not my 25.6k. - Same conversation both times. Eight messages, unchanged between captures, so the delta is attributable to the tool schemas and nothing else. That is the strength of the measurement and also its narrowness.
/contextreports, it does not itemize. It gives one number for all tools together. It cannot tell you which single schema is the expensive one — which is exactly the gap that led to the next section.- List prices, not your invoice. The dollar figures use published rates. Promos, contracts and caching all move the real number.
- Removable is not the same as should-be-removed. Background jobs and multi-agent runs genuinely depend on some of what looks like bloat. Cut what you do not use.
Why I built Iris
/context gives you one number for all tools together. It tells you the total is 25.6k;
it will not tell you that one schema is 5.3k of it, or that nine of your thirteen tools were never
called once all session.
Claude Code talks to the API over plain HTTP, so you can put a small proxy in between and read the actual payload. That started as a logging script and turned into Iris — a local proxy that shows what each request carried, which schemas shipped without ever being used, and what removing them would save, measured on your configuration rather than mine.
It grew a second half along the way. Once you are already sitting between the agent and the API, the same position lets you gate what the agent is allowed to do — which is Guard.
# see your own numbers
npx @zero-drift/iris
The product page for what Iris does, or the Quickstart to have your own before/after in about five minutes.