tokenbrake

Claude Code hooks · v0.2.0 · MIT

Brakes for your Claude Code context window.

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 dependencies Node 18+ No Python, no Rust, no Git Bash Works on Windows Nothing 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.

request context re-read per request 1 2 3 4 5 6 7 60 one 30k result, carried 57 more times
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 · modelAPI costCache-read tokensSaved
Audit · Fable 5.1$8.40 → $7.024.62M → 2.72M−16%
Audit · Opus 5$5.97 → $3.775.77M → 3.99M−37%
Debugging · Opus 5guard never fired; the model bounded its own reads≈ 0%
Debugging · Fable 5.1three 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.

{
  "maxChars": 6000,
  "headLines": 40,
  "tailLines": 40,
  "keepErrorLines": 20,
  "readMaxBytes": 60000,
  "readLimitLines": 300,
  "logAllTools": true,
  "enabled": true
}

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