---
title: "Claude Code, on a budget"
description: "One two-day chat reached 800k tokens and seven eighths of it was never needed again. The guide I wrote for my team: five settings and a few habits that halved the cost per message."
date: 2026-09-15
updated: 2026-10-13
author: "Ibrahem Mahyob"
canonical: https://ibrahemid.com/blog/claude-code-context-budget
tags: ["Claude Code", "AI", "Workflow"]
---

# Claude Code, on a budget.

One two-day chat reached 800k tokens and seven eighths of it was never needed again. The guide I wrote for my team: five settings and a few habits that halved the cost per message.

**TL;DR**

Every message re-sends the whole chat. One two-day chat on my team reached **800k tokens**, and about seven eighths of it was material the model never needed again.

Five settings fix most of it: a compaction window, a cap on command output, a cap on helper reports, two ready-made helper agents, and a script that turns test logs into one line per check. Per-message cost fell by more than half.

## Where 800k went

I wrote this for my team after reading through our saved chats. The version they got is deliberately plain: no internals, no theory, just what the limit is spent on and the settings that stop it. This is that guide, lightly reshaped for a blog.

The chat that started it was two days long. The developer typed 15 messages. The chat still reached 800k tokens.

| Tokens | What it was |
| --- | --- |
| **316k** | Project docs pasted into the chat, then pasted again to helpers |
| **300k** | Reports from helper agents, some as long as an essay |
| **45k** | System messages |
| **40k** | Two large files read top to bottom |
| **~100k** | The actual work |

> Seven eighths of the chat was material Claude did not need again, and every message paid for it.

## How Claude Code uses your limit

Claude has no memory between messages. Every time you send one, it re-reads the whole chat from the top: your messages, its replies, every file it opened, every command output. That whole pile is the context. A token is roughly three quarters of a word.

Your plan limit is charged on the size of that pile, every message. A chat that has grown to 800k tokens costs about eight times more per message than one at 100k, even if you only type “ok”.

## Four words you will see

<table><tbody><tr><td><strong>Session</strong></td><td>One chat in Claude Code, from start to <code>/clear</code>.</td></tr><tr><td><strong>Helper agent</strong></td><td>A second Claude that the main one sends off to do a task. Only its final report comes back into your chat. The docs call it a subagent.</td></tr><tr><td><strong>Compaction</strong></td><td>When the chat gets too big, Claude replaces the history with a short summary. Anything not in the summary or in a file is forgotten.</td></tr><tr><td><strong>Cache</strong></td><td>For one hour after your last message on a subscription, five minutes on usage credits or an API key, Claude keeps what it already read, so re-reading is cheap. After that it starts over at full price.</td></tr></tbody></table>

## Five limits that fix it

These are settings, not rules Claude has to remember. Once set, they apply to every project on your machine.

| Limit | What it does |
| --- | --- |
| **Compaction at 600k** | Claude summarizes the chat when it reaches 600k tokens. Without this, models with a 1M window summarize at about 967k, so a chat can grow to 900k and every message pays for it. We tried 350k first: summaries came 11 times more often, in the middle of tasks, and work got lost. 600k is the middle ground. |
| **Command output capped at 12k characters** | When a command prints more than 12k characters, Claude Code writes the output to a file and hands Claude the path and the first 2k. A long test log can no longer fill the chat by accident, and nothing is lost. |
| **Helper reports under 180 words** | A helper agent that writes a long report is sent back to shorten it. The detail goes into a file; the chat gets five short lines. |
| **Helper instructions** | Two ready-made helper agents (one builds, one checks) that already know the report format and the rule below. |
| **One-line test results** | A small script runs your typecheck, lint and tests and prints one line per check: `PASS test 12s` or `FAIL test 12s log: path`. Claude reads the log only when something fails. |

## Set it up: paste these into Claude Code

One prompt per message, in order. Claude makes the change and shows you what it changed. Prompts 1 to 3 are done once per machine; 4 and 5 once per project.

\[1\]

### Settings

Prompt 1

`~/.claude/settings.json`

```
Edit ~/.claude/settings.json. Keep every existing key.
Apply exactly these changes and nothing else:

1. Set the top-level key "autoCompactWindow" to the number 600000.
2. Set the top-level key "bashOutputMaxChars" to the number 12000.
3. In "env", set "CLAUDE_REPORT_MAX_WORDS" to "180" (string value).
   If "env" contains "BASH_MAX_OUTPUT_LENGTH", remove it; the setting
   above replaces it.
4. If "env" contains "CLAUDE_CODE_AUTO_COMPACT_WINDOW", remove it.
   It overrides the /autocompact command and must not be set.
5. If "modelSettings" has an "autoCompactWindow" under any model,
   remove that key. A per-model value overrides the number above.

Validate the file parses as JSON after the edit. Show me the diff.
Do not touch permissions, hooks, plugins or any other key.
```

\[2\]

### Short helper reports

The hook script first, then the prompt that installs it.

`~/.claude/hooks/subagent-report-gate.sh`

```bash
#!/bin/bash
# SubagentStop hook: the final message is all the parent keeps in context.
# Block reports over the word cap so the agent rewrites to the template.
INPUT=$(cat)
MAX_WORDS="${CLAUDE_REPORT_MAX_WORDS:-180}"
[ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ] && exit 0
MSG=$(echo "$INPUT" | jq -r '.last_assistant_message // empty')
[ -z "$MSG" ] && exit 0
WORDS=$(echo "$MSG" | wc -w | tr -d ' ')
[ "$WORDS" -le "$MAX_WORDS" ] && exit 0
jq -n --arg n "$WORDS" --arg max "$MAX_WORDS" '{
  decision: "block",
  reason: ("Final report is \($n) words; cap is \($max). Write the full detail to a file (.status/reports/ if the project has .status/, else the session scratchpad) and reply with only: Result: <done|blocked|failed>. Changed: <paths>. Gates: <one line per check>. Flags: <one line each, or none>. Detail: <report path>.")
}'
```

Prompt 2

`SubagentStop hook`

```
Create ~/.claude/hooks/subagent-report-gate.sh with exactly the
script above, then chmod +x it.

Then register it in ~/.claude/settings.json under "hooks" ->
"SubagentStop" as a command hook: command
"~/.claude/hooks/subagent-report-gate.sh", timeout 10. Merge with
any existing hooks; do not remove any.

Test it: pipe a JSON object with stop_hook_active false and a
200-word last_assistant_message into the script and confirm it
prints a block decision; pipe a 20-word message and confirm it
exits silently. jq must be installed; if it is not, stop and
tell me.

Show me the settings diff.
```

\[3\]

### Helper agents

Prompt 3

`~/.claude/agents/`

```
Create two subagent definitions in ~/.claude/agents/. If a file
already exists, merge: keep its frontmatter and body, append the
report block below.

File implementer.md, frontmatter: name implementer, description
"Executes one scoped implementation task from an approved plan.",
model sonnet. Body:

Implement exactly the task given, following the plan and the
project's existing patterns and CLAUDE.md rules. Read the files
you touch before editing. Run the project's typecheck and tests
for what you changed. Do not refactor unrelated code, do not add
features beyond the task, do not touch other plan tasks. If the
task conflicts with the architecture or is blocked, stop and
report instead of working around it.

File verifier.md, frontmatter: name verifier, description
"Adversarially verifies completed work: runs tests and lint,
checks claims against actual behavior, hunts for regressions.",
model sonnet. Body:

Verify the claimed work. Run the project's test suite and linter
for the affected area. Read the diff and try to falsify each
claim. Exercise the changed behavior directly when possible. Do
not fix anything.

Append this block to both bodies, verbatim:

Final message cap: 180 words. It is the only thing the
orchestrator reads, and it costs context on every later turn.
Put evidence, logs, diffs and reasoning in
.status/reports/<task-slug>.md and cite the path. Reply in this
shape only:
Result: <done|blocked|failed>. Changed: <paths>. Gates: <one
line per check; use scripts/gate.sh when the repo has it>.
Flags: <one line each, or none>. Detail: <report path>.
Never paste file contents, test output or diffs into the reply.
If the prompt inlines a file you were also told to read, read
the file and ignore the inline copy.

Show me both files when done.
```

Sonnet is the budget choice. I have since moved implementer and verifier to Opus and added a third agent, mechanic, on Sonnet for renames and cleanup. The pin matters more than the model: an unpinned helper inherits the session model, and on Fable that is the most expensive one.

\[4\]

### One-line test results (per project)

Prompt 4

`scripts/gate.sh`

```
Create scripts/gate.sh in this repo. Detect the package manager
from the lockfile (pnpm, yarn, npm) and the available checks from
package.json scripts and config files: typecheck (tsc --noEmit if
tsconfig exists), lint (the lint script if present), test (the
test script if present, run non-interactively with a dot or
minimal reporter), build (only when I pass it explicitly). For a
monorepo, run each check at the workspace root.

Behaviour:
- Usage: scripts/gate.sh [tsc|lint|test|build]... with no args
  meaning tsc lint test.
- Each check prints exactly one line: "PASS name Ns" or
  "FAIL name Ns log: path".
- On FAIL, also print at most 25 lines grepped from the log: TS
  errors for tsc, error/warning lines for lint, failing test
  names and assertion messages for test.
- Full output goes to .status/gate-logs/name.log. Add .status/
  to .gitignore if it is not already ignored.
- Exit non-zero if any check failed.
- cd to the repo root first (git rev-parse --show-toplevel).

chmod +x it. Run scripts/gate.sh tsc and show me the output. Add
"Bash(scripts/gate.sh*)" to permissions.allow in
.claude/settings.json (create the file if missing, merge if
present).
```

\[5\]

### Project rules (per project)

Prompt 5

`CLAUDE.md`

```
Append this section to the repo's CLAUDE.md verbatim (create the
file if missing). Do not rewrite anything else in it.

## Context budget
- Run typecheck, lint and tests through scripts/gate.sh, never
  raw. Read a failure's log file only for the failing check.
- Agent prompts pass file paths, never file contents. Read files
  in ranges; whole-file reads only under ~150 lines.
- Subagent reports follow the five-field template (Result,
  Changed, Gates, Flags, Detail) with detail written to
  .status/reports/.
- One task per session. Before leaving, run /handoff with what
  the next session will do; the next session starts fresh from
  that file. Never resume a session after a gap over an hour:
  the prompt cache has expired and resuming reprocesses the
  whole history.
- /clear between unrelated tasks. /compact at a natural break
  with a focus instruction, not mid-task.

Show me the diff.
```

1.  Prompt 1: `autoCompactWindow` 600000, `bashOutputMaxChars` 12000, report cap 180 words in `~/.claude/settings.json`.
2.  Prompt 2: the SubagentStop hook, registered and tested with a long and a short message.
3.  Prompt 3: `implementer.md` and `verifier.md` in `~/.claude/agents/` with the report block.
4.  Prompt 4, per repo: `scripts/gate.sh`, one line per check.
5.  Prompt 5, per repo: the context budget section in `CLAUDE.md`.
6.  `/hooks`, `/autocompact`, `/context` show the new values.

## Habits

\[01\]

### New task, new chat.

Type `/clear` when you switch to something unrelated. The old chat is still charged on every message until you do.

\[02\]

### Choose the model before you start.

Switching model mid-chat makes Claude re-read everything at full price. Turning on fast mode does the same, once. Changing effort used to as well, but on Opus 5.5, Sonnet 5.5, Haiku 5.5 and Fable 5.1 it keeps the cache on a subscription or API key.

\[03\]

### Been away over an hour? Start a new chat.

The cache is gone, and continuing the old chat re-reads all of it. Pro and Max now offer to resume from a summary instead, which still re-reads everything once to write it. A new chat costs nothing.

\[04\]

### Carry the context over with `/handoff`.

Before you leave a chat, type `/handoff` and what the next chat will do, for example `/handoff finish the invoice export`. It writes a short summary file. In the new chat, paste the file path and continue. Install once: `claude plugins install mattpocock-skills`.

\[05\]

### Give file paths, not file contents.

Tell Claude which file and which lines. Do not paste the file.

\[06\]

### Summarize on your terms.

At a natural break, type `/compact keep the failing test names and the API shape`. That beats an automatic summary in the middle of a task.

\[07\]

### Need one long chat?

Start it with `claude --autocompact 1M`. The flag lasts one launch. Typing `/autocompact 1M` instead saves 1M for the current model in your settings, and `/autocompact auto` does not bring back 600k: it restores the model’s own window, about 967k. If you did that, type `/autocompact 600k`.

\[08\]

### Prune command output per task.

The 12k cap is a ceiling, not a filter. The `jevprune` plugin runs after each command and keeps only the lines relevant to the task, in their original order; the dropped lines stay on disk if the model needs them. Install once: `claude plugins install jevprune`.

## Check it worked

<table><tbody><tr><td><code>/hooks</code></td><td>Shows <code>subagent-report-gate.sh</code> under SubagentStop.</td></tr><tr><td><code>/autocompact</code></td><td>Shows 600k.</td></tr><tr><td><code>/context</code></td><td>Shows what is in the chat right now and how big it is.</td></tr><tr><td><code>/usage</code></td><td>Shows plan usage, a Prompt cache (main) line with the hit rate and the likely cause of the last miss, and a flag when long context or cache misses take 10% or more of recent usage.</td></tr></tbody></table>

What you get once it is on:

#### before

-   Long chats grew to 700k to 1M and were never summarized.
-   Every message re-read about half a million tokens.
-   Helper reports came back at up to 4.4k words and stayed in the chat.
-   Continuing a chat the next morning re-read all of it at full price, 5 times in one chat.

#### after

-   Helper reports are five lines; test results are one line per check.
-   Per-message cost fell by more than half.
-   At 350k, summaries came too often and lost work mid-task. Now 600k, which you can change per chat.
-   Faster replies. Less to re-read means less waiting.

## Where this comes from

Three kinds of source. **docs**: Anthropic’s own Claude Code documentation. **measured**: 157 chats on one developer’s machine, 82 before these limits and 75 after. **our choice**: a number we picked from that data. Last checked against Claude Code 2.1.296 with Opus 5.5 as the default model.

| Point | Source | In short |
| --- | --- | --- |
| Every message re-reads the whole chat | docs | “Claude Code sends your full conversation with every request.” |
| 1M models summarize only near the top | docs | Without a limit set, models with a native 1M window compact at about 967k tokens. |
| Where the 800k went | measured | Read from the saved chat: helper reports 300k, pasted docs 316k, big files 40k. |
| 350k was too low | measured | 6 summaries in 82 chats before; 40 in 75 chats after, nearly all mid-task. |
| 600k | our choice | Halfway between the limit that hurt and no limit. Unchanged after four weeks. |
| Cut command output, shorten helper reports | docs | Anthropic’s cost guide shows a hook that trims test output to failures only, and says to send noisy work to helpers so only a summary returns. |
| New chat per task, summarize at a break | docs | “Clear between tasks” and “run /compact at a natural break” are the first two tips on the cost page. |
| Pick the model first, start fresh after an hour | docs | Each model keeps its own cache. On Opus 5.5, Sonnet 5.5, Haiku 5.5 and Fable 5.1 an effort change keeps it. The cache lasts one hour on a subscription within plan usage, five minutes on usage credits or an API key. |

Pages: [Manage costs](https://code.claude.com/docs/en/costs), [Prompt caching in Claude Code](https://code.claude.com/docs/en/prompt-caching), [Model configuration](https://code.claude.com/docs/en/model-config), [Tools reference](https://code.claude.com/docs/en/tools-reference#output-limits).

## For team leads

-   The 600k window now applies to every default. Opus 5.5, Sonnet 5.5, Haiku 5.5 and the Fable models run with a 1M window and compact at about 967k on their own. A 200k window remains only on Opus 4.6 and Sonnet 4.6 without `[1m]`, or with `CLAUDE_CODE_DISABLE_1M_CONTEXT=1`. There the window is clamped to 200k and changes nothing, and the other four limits still apply.
-   It is a setting on purpose. The `CLAUDE_CODE_AUTO_COMPACT_WINDOW` environment variable does the same thing but silently overrides `/autocompact`, so nobody can raise it for one long chat.
-   Since Claude Code 2.1.288, `/autocompact` saves its value per model under `modelSettings`, and that entry overrides the top-level number for that model in later sessions too. Prompt 1 clears those entries. For one long chat use the `--autocompact` flag, which lasts one launch.
-   There is no free number. A lower window means cheaper messages and more mid-task summaries. 600k is a chosen point. Re-measure after a few weeks and move it.
-   Rollout. Prompts 1 to 3 once per developer machine. Prompts 4 and 5 commit `scripts/gate.sh`, `.claude/settings.json` and `CLAUDE.md` to the repo, so new joiners get them on clone.
-   The 12k cap hides nothing. Over it, Claude Code saves the full output to a file in the session directory and gives Claude the path and a 2k preview, so it reads or greps the rest when it needs it. Set `bashOutputMaxChars` (Claude Code 2.1.261 or later) rather than the older `BASH_MAX_OUTPUT_LENGTH` variable: the setting sizes the inline cap and the read-back window together, and the variable is ignored once the setting exists.
-   To measure on your own machines: `/usage` shows a Prompt cache (main) line (requests, share of input served from cache, misses with the likely cause) and a plan breakdown that flags long context and cache misses, by day or week. The JSONL transcripts under `~/.claude/projects/` still work for one-off counts (`isCompactSummary` per file for summaries, `input_tokens` plus `cache_read_input_tokens` on assistant messages for context size), but the format is internal and changes between versions.
-   The one-hour cache holds only within plan usage. Once a developer is on usage credits, Claude Code drops to five minutes. `promptCacheTtl: "1h"` in settings keeps the hour (2.1.242 or later). It pays off when people idle between turns, since the hour costs more per cache write.
-   To pin every helper’s model from settings instead of frontmatter, set `CLAUDE_CODE_SUBAGENT_MODEL` to the model and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE` to 1 in `env` (2.1.257 or later). Frontmatter is then ignored, so it fits a fleet on one helper model, not the Opus-plus-Sonnet split above.
-   Opus 5.5 defaults to medium effort in Claude Code, where earlier Opus models ran high or xhigh, and its thinking cannot be turned off. Effort is the thinking lever. A top-level `effortLevel` in user settings does not reach Opus 5.5 and later. Save a level per model with `/effort` or `modelSettings`.

> The limit is spent on what the model re-reads, not on what you type. Cap what comes back and the rest takes care of itself.
