Research

The Token Tax

What Claude Code carries before you type anything — measured, cut, and verified with /context.

The short answer

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 requestWhat it holdsSize
System promptIdentity, rules, tone, environment info, feature instructions~2–3k
Tool definitionsFull JSON schema for every enabled tool — the biggest block~3–26k
Skills catalogueDescriptions of every bundled and custom skillcuttable
Memory filesCLAUDE.md, global and project, verbatim~0.2–2k
MCP tool schemasEvery registered server, whether you use it or notcuttable
Conversation historyAll prior messages plus every tool resultgrows
Your messageThe only part you typedtiny

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

System prompt2.4k
System tools25.6k
Messages8
Baseline28.0k

After5.6k / 1.0M

System prompt2.4k
System tools3.2k
Messages8
Baseline5.6k
Before · first 28k of the window28.0k at message zero
2.4ksystem tools 25.6k
After · same scale5.6k at message zero
2.4k3.2kreclaimed

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

Niche editors and integrations

Background and remote machinery

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"]
  }
}
Bare versus scoped — the detail that matters

"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.

FlagWhat it removes
disableWorkflowsThe multi-agent Workflow tool — typically the largest single line in the payload, ~5.3k of schema alone. The biggest single win.
disableBundledSkillsAll bundled skills from the catalogue Claude reads, while their slash commands stay typable. All-or-nothing; for per-skill control use skillOverrides
disableRemoteControlMachinery for controlling sessions from other devices
disableClaudeAiConnectorsclaude.ai connector integration reaching into your coding session
disableArtifactThe 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"
}
Check your MCP servers first

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.

ToolWhy it stays
BashCut it and Claude cannot run your tests, git, or build. The agent goes blind
ReadWithout it Claude cannot look at your code before changing it
EditThe precise way to change code; removing it forces slow full-file rewrites
WriteNeeded for new files; without it Claude can only patch what exists
GlobHow Claude locates files fast; cut it and it falls back to slow shell hunting
GrepFinds 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.

Never trim for safety

"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 onceGrown to 1M over 100 turnsSame, 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

  1. 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.
  2. Clear early, compact at milestones. /clear wipes history between unrelated asks; /compact summarizes it. Run it at a good stopping point — when tests pass, when a feature is done — not when the window is 90% full.
  3. 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.
  4. 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.
  5. 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.
  6. 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.4k
tokens / call
not shipped after the trim
80%
less overhead
initial context baseline
$0.112
saved / call
uncached Opus input, list rate
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:

SettingEffect
CLAUDE_CODE_DISABLE_1M_CONTEXT=trueKeeps you on the normal window if you do not need 1M
DISABLE_NON_ESSENTIAL_MODEL_CALLS=1Skips small background calls for things like chat titles
CLAUDE_CODE_MAX_OUTPUT_TOKENSCaps 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.

  1. Baseline it. Run /context in a fresh session. Write the numbers down.
  2. Drop in the settings. ~/.claude/settings.json for everything, or .claude/settings.json per project. Merge; do not clobber existing keys.
  3. Restart the session. Settings load at session start. In Cursor, close and reopen the Claude panel or reload the window.
  4. Re-measure. /context again. The delta is what you were shipping every turn and now are not.
  5. 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.

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
Start here

The product page for what Iris does, or the Quickstart to have your own before/after in about five minutes.