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.

Read12 minSetup15 minStackanyDate2026-09-15
00

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.

TokensWhat it was
316kProject docs pasted into the chat, then pasted again to helpers
300kReports from helper agents, some as long as an essay
45kSystem messages
40kTwo large files read top to bottom
~100kThe actual work
# Seven eighths of the chat was material Claude did not need again, and every message paid for it.
01

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

02

Four words you will see

SessionOne chat in Claude Code, from start to /clear.
Helper agentA 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.
CompactionWhen the chat gets too big, Claude replaces the history with a short summary. Anything not in the summary or in a file is forgotten.
CacheFor one hour after your last message, Claude keeps what it already read, so re-reading is cheap. After an hour it starts over at full price.
03

Five limits that fix it

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

LimitWhat it does
Compaction at 600kClaude summarizes the chat when it reaches 600k tokens. Without this, models with a 1M window never summarize, 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 cut at 12k charactersWhen Claude runs a command, only the first 12k characters come back. A long test log can no longer fill the chat by accident.
Helper reports under 180 wordsA 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 instructionsTwo ready-made helper agents (one builds, one checks) that already know the report format and the rule below.
One-line test resultsA 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.
04

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. In "env", set "BASH_MAX_OUTPUT_LENGTH" to "12000" and
   "CLAUDE_REPORT_MAX_WORDS" to "180" (string values).
3. If "env" contains "CLAUDE_CODE_AUTO_COMPACT_WINDOW", remove it.
   It overrides the /autocompact command and must not be set.

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
#!/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.
[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.
05

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 or effort mid-chat makes Claude re-read everything at full price.

[03]

Been away over an hour? Start a new chat.

The cache is gone; continuing the old chat re-reads all of it.

[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?

Type /autocompact 1M at the start of that chat. /autocompact auto puts it back.

06

Check it worked

/hooksShows subagent-report-gate.sh under SubagentStop.
/autocompactShows 600k.
/contextShows what is in the chat right now and how big it is.
/usageShows how much of your plan the chat is using.

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

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.

PointSourceIn short
Every message re-reads the whole chatdocs“Claude Code sends your full conversation with every request.”
1M models never summarize on their owndocsWithout a limit set, compaction happens only when the model’s window is full.
Where the 800k wentmeasuredRead from the saved chat: helper reports 300k, pasted docs 316k, big files 40k.
350k was too lowmeasured6 summaries in 82 chats before; 40 in 75 chats after, nearly all mid-task.
600kour choiceHalfway between the limit that hurt and no limit. We will check again in two weeks.
Cut command output, shorten helper reportsdocsAnthropic’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 breakdocs“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 hourdocsEach model keeps its own cache; the cache lasts one hour on a subscription.

Pages: Manage costs, Prompt caching, Model configuration.

08

For team leads

  • The 600k window only bites on 1M-context models (Fable, Opus 5 with 1M, Sonnet 5). On a 200k model it is clamped to 200k and changes nothing; 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.
  • There is no free number. A lower window means cheaper messages and more mid-task summaries. 600k is a chosen point; re-measure in two 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 output cap can hide output someone asked for. The full text is still in the command; ask Claude to write it to a file and read the part you need.
  • To measure on your own machines: every chat is saved as a JSONL file under ~/.claude/projects/. Count lines with isCompactSummary per file for summaries, and sum input_tokens plus cache_read_input_tokens on assistant messages for context size.
# 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.