Tool results are the bulk of what a session spends, and shell output is the worst offender.
tokenbrake trims oversized output and caps unbounded file reads before they reach the model,
then tells you afterwards what ate your tokens.
Zero dependenciesNode 18+No Python, no Rust, no Git BashWorks on WindowsNothing leaves your machine
As a plugin
$ claude plugin marketplace add 33kain/tokenbrake
$ claude plugin install tokenbrake@tokenbrake
Installs without touching a settings file.
Or with npx
$ npx tokenbrake init # every project (~/.claude/settings.json)$ npx tokenbrake init --project # this project only, commit to share
Writes the two hooks into a settings file you own. Pick one method, not both.
Restart Claude Code, then npx tokenbrake status shows what is installed and spawns each hook once,
exactly as Claude Code does. If it prints FAILED to start, that is the hook Claude Code would also fail to start, silently.
A tool result is not paid for once.
Every tool result is re-sent as context on every later request until the session compacts.
A 30k-token test dump at request 3 of 60 is read 57 times. Its cost is its size times the
requests it is carried through, and that product, not the size alone, says which single
cat, Read or test run should never have been let in.
Claude Code's own ceiling for a Bash result is about 30,000 characters inline, roughly 7,500 tokens.
tokenbrake lowers that ceiling to something sane and keeps the parts that matter.
Cost of a result = size × the requests it is carried through.
What it does
Shell output trim
PostToolUse · Bash, PowerShell
Output over 6,000 characters is replaced by the first 40 lines, the last 40, and up to 20 lines from
the middle that look like errors or warnings, each with its line number. The full output is saved to
~/.claude/tokenbrake/out/ and the trimmed result names the path, so Claude can Grep
or Read it if it needs more.
Nothing is rewritten or "compressed". What Claude sees is a predictable head/tail excerpt of the real output.
Large-read cap
PreToolUse · Read
A Read with no offset or limit on a file over 60 KB is rewritten to
limit: 300, and Claude is told the file's real size and how to page through it or Grep first.
Reads that already specify a range are untouched. Images, PDFs and notebooks are skipped.
Ledger
Every tool result
The size of each result is appended to ~/.claude/tokenbrake/ledger.jsonl, with the tool-use id
that joins it to the session transcript. This is what the report reads.
It is a local file. Nothing is sent anywhere.
What Claude sees instead of 412 lines
A test run that used to arrive as 11,441 characters, trimmed by the guard. The failing test in the middle is kept; the passing noise around it is not.
✔ router › case 1 (3 ms)
✔ auth › case 2 (6 ms)
✔ db › case 3 (9 ms)
… 37 more lines of head …[tokenbrake] 332 lines omitted here (11,441 chars total). Full output saved to ~/.claude/tokenbrake/out/a1f4c9e2-toolu_01Hx.txt — Read or Grep it if you need more.[tokenbrake] error/warning-looking lines from the omitted region: L203: ✖ auth › refresh token rotates on use (312 ms) L204: Error: expected 401, got 200
✔ db › case 373 (52 ms)
✔ cli › case 374 (55 ms)
… 36 more lines of tail …
✔ router › case 411 (69 ms)
✔ auth › case 412 (72 ms)
What ate your tokens
$ npx tokenbrake report # last session$ npx tokenbrake report --all # one line per session on disk$ npx tokenbrake report --session=<prefix> # a particular one$ npx tokenbrake report --top=25 # widen the ranking
report reads the Claude Code session transcript, every tool result exactly as the model saw it and the API's
usage per request, and ranks results by size × the requests they were carried through. It also shows what the
session processed in total, how much came from cache, what the context holds right now, and which results tokenbrake trimmed.
Session a1f4c9e2… /home/mili/app
22 requests, 8 tool results
Context processed: 1.6M tokens across 22 requests (95% read from cache); output 6k
Context now: ≈ 114k tokens — what the next request re-reads
Tool results entered ≈ 65k tokens of context, carried through later requests ≈ 1.3M token-reads
What ate it — by tokens carried (size × later requests), top 6:
size carried turns tool what
30k 620k 21 Bash npm test
23k 460k 20 Read /home/mili/app/package-lock.json
7k 128k 19 Bash git log --stat -40
4k 63k 18 Read /home/mili/app/src/server.js
2k 23k 15 Read /home/mili/app/CHANGELOG.md
600 10k 16 Bash node build.mjs
By tool (share of tokens carried):
Bash 4 calls 37k entered 760k carried 58%
Read 3 calls 28k entered 546k carried 42%
Grep 1 calls 225 entered 4k carried 0%
One result to have brakes on: Bash "npm test" — ≈ 30k tokens carried 21 times. The tokenbrake guard would have trimmed it to its head, tail and error lines.
Sizes are chars/4 estimates; the usage line is what the API reported.
Measured, not promised
The same read-only audit task on the same repository, run as a Cowork session without the hooks and with them, on two
models. Then a five-fault debugging task, twice. Every answer was identical between arms, and the five-hour usage limit moved with the cost.
Task · model
API cost
Cache-read tokens
Saved
Audit · Fable 5.1
$8.40 → $7.02
4.62M → 2.72M
−16%
Audit · Opus 5
$5.97 → $3.77
5.77M → 3.99M
−37%
Debugging · Opus 5
guard never fired; the model bounded its own reads
≈ 0%
Debugging · Fable 5.1
three 2k outputs trimmed against 1.7M cache reads
≈ 1%
The saving is whatever the model would otherwise have let in: 0% to 37% across these runs. On the debugging task both
models already piped through tail -80 and grep, so there was nothing to trim. The report's
"Tool results entered" line says which end of that range a session was on.
The protocol and every number are in AB-TASK.md.
Configure
Optional ~/.claude/tokenbrake.json (or under CLAUDE_CONFIG_DIR). These are the defaults.
enabled: false turns the guard off without uninstalling. logAllTools: false records only trimmed and capped events.
$ npx tokenbrake uninstall [--project]
$ npx tokenbrake clean --days=7 # delete saved full outputs older than 7 days
Notes and limits
Rewriting a tool result needs updatedToolOutput support in PostToolUse, which Claude Code added for built-in tools in the v2.1.12x line. On older versions the hook runs but changes nothing.
Only successful tool calls pass through PostToolUse. A failing command already arrives as a ~10,000-character head/tail excerpt from Claude Code itself; tokenbrake doesn't touch it.
The guard fails open. Any error exits 0 with no output and Claude Code proceeds unchanged.
One node process per tool call, about 50–100 ms. Set the PostToolUse matcher to Bash|PowerShell|Read if you want it lighter and don't need the full ledger.
Reads capped by tokenbrake are partial views. Edit still requires an exact string match, so a capped read can't cause a wrong edit; Claude reads the section it needs first.