Kompactor: Cut the Tokens Claude Code Spends on Noise

By stackcone · October 2026

Open a long Claude Code session and look at where the tokens went. Very little of it is your prompt or Claude's answer. Most of it is tool output: a test run with four hundred passing tests, a dependency install, git log, a 1,600-line file read from top to bottom. Claude Code sends all of that again on every later turn, until the context fills up and gets compacted.

We kept paying for that noise on client projects, so we built Kompactor: a free, open-source Claude Code plugin that shortens tool output before Claude reads it, while keeping every error, failing test, stack frame from your own code and summary line. This article explains the problem, how Kompactor works, what it changed in 54 measured Claude Code sessions, and how to install it.

−16%tokens per session
−10%cost per session
−22%output tokens
27/27answers still correct

Where Claude Code's tokens actually go

Claude Code is an agent loop. Each turn, the model sees the whole conversation so far, decides on a tool call (run a command, read a file, search), gets the result, and continues. The result of every tool call joins the context, and the context is sent again on the next API call. Prompt caching makes the repeats cheaper, but they're still billed, and they still fill the window.

flowchart LR classDef q fill:#f1f5f9,stroke:#64748b,color:#0f172a classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d P["Your prompt\n~200 tokens"]:::ok --> T1["Turn 1: run tests"]:::q T1 --> O1["Test output\n~9,800 tokens"]:::bad O1 --> T2["Turn 2: read a file"]:::q T2 --> O2["Whole file\n~14,000 tokens"]:::bad O2 --> T3["Turn 3, 4, 5…"]:::q T3 --> R["Every turn re-sends\nall of the above"]:::bad

The numbers in that diagram are real: a Flask pytest -v run with one failure is about 9,800 tokens, and reading Flask's app.py is about 14,000. Claude needs the one failing test and the few functions it's looking for. Everything else is noise you pay for on every remaining turn.

Why failing commands are the worst case

Claude Code has a hook system that lets a plugin rewrite a tool's output before Claude sees it. But there's a catch: when a command exits non-zero, output hooks don't run, and Claude Code itself cuts the output to roughly the first and last 5,000 characters. With a large test suite, the failures in the middle are exactly what gets cut.

You can see Claude work around this. It re-runs the command piped through | tail -150 or | grep FAIL, sometimes three or four times, and each extra run is another full turn that re-sends the whole context. Failing tests, broken builds and lint errors are the moments you most want Claude to see clearly, and they're the moments it sees worst.

How Kompactor works

Kompactor plugs into Claude Code at three points: before a file read, after every tool call, and around shell commands through Claude Code's CLAUDE_CODE_SHELL_PREFIX setting. You never call it. It acts on Claude's tool calls by itself.

flowchart TD classDef q fill:#f1f5f9,stroke:#64748b,color:#0f172a classDef k fill:#ffedd5,stroke:#ea580c,color:#7c2d12 classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d C["Claude decides on a tool call"]:::q --> D{"Which tool?"}:::q D -->|"Read a big file"| R["Kompactor: outline first\nsymbols, line ranges, who imports it"]:::k D -->|"Bash command"| S["Kompactor shell prefix\nruns it to completion, keeps the exit code"]:::k D -->|"Grep / Glob"| G["Tool runs normally"]:::q S --> P["Kompactor compacts the output\ndrops passing tests, progress bars, library frames"]:::k G --> P P --> St["Raw copy stashed locally\nkompactor show <id>"]:::q R --> B["Claude gets the short version\nerrors, failures, summaries kept"]:::ok P --> B B --> C
When Claude…Kompactor…Claude Code hook
runs a commandDrops passing tests, progress bars, download chatter, library stack frames and lock-file diffs. Keeps errors, failures, your own frames and summary lines.PostToolUse
runs a command that failsRuns tests, builds, linters and installs to completion, compacts the whole output and passes the exit code through unchanged.Shell prefix
gets more than ~30K characters backClaude Code would save it to a file and show a 2 KB preview. Kompactor compacts the whole output instead.PostToolUse
opens a big source fileReturns its outline first: symbols with line ranges, signatures, who imports it and which tests cover it. Repeating the read returns the whole file.PreToolUse
first searches the codeAdds a short map of the most depended-on modules, once per session.PostToolUse
writes a replyGets a few lines of rules: answer first, smallest diff, reuse before writing. 0 to 177 tokens depending on the level.SessionStart
finishes a turnShows what the turn saved.Stop

Three safety rules hold everywhere. Nothing is lost: every shortened output keeps a raw copy that Claude can fetch with kompactor show <id>, and the id is printed in the short version. Nothing is blocked: an outline-first read is "deny once, allow on retry", so Claude can always get the full file. Nothing breaks: every hook fails open, so if Kompactor hits an error, the original output goes through untouched. The shell prefix runs after Claude Code's permission check, on Claude's original command, so your permission rules apply exactly as before.

A real before and after

This is from the zustand repository: 225 tests, one failing. Claude was asked to run npx vitest run --reporter=verbose and say which test fails and why. Both versions are copied from the session transcripts of the measured runs.

Without Kompactor: 15,617 characters

Claude added its own | tail -150 to keep the output manageable. The 150 lines it got back were mostly passing tests; the failure only happened to land near the end.

$ npx vitest run --reporter=verbose 2>&1 | tail -150
 ✓ |zustand| tests/devtools.test.tsx > when create devtools was called multiple times … 1ms
 ✓ |zustand| tests/devtools.test.tsx > when create devtools was called multiple times … 1ms
 … about 120 more lines of passing tests (14,117 characters) …
 ✓ |zustand| tests/vanilla/subscribe.test.tsx > subscribe() > should call listener … 1ms

 FAIL  |zustand| tests/zz-kompact.test.ts > counter store > counts every increment
AssertionError: expected 2 to be 3 // Object.is equality
 Test Files  1 failed | 13 passed (14)
      Tests  1 failed | 224 passed (225)

With Kompactor: 1,039 characters

Claude ran the command plainly. Kompactor's shell prefix ran it to completion, compacted it and kept the exit code, so Claude Code still reported the failure.

$ npx vitest run --reporter=verbose
Exit code 1

 × |zustand| tests/zz-kompact.test.ts > counter store > counts every increment 8ms
   → expected 2 to be 3 // Object.is equality

 FAIL  |zustand| tests/zz-kompact.test.ts > counter store > counts every increment
AssertionError: expected 2 to be 3 // Object.is equality
- Expected
+ Received
- 3
+ 2
 ❯ tests/zz-kompact.test.ts:12:36
     12|     expect(store.getState().count).toBe(3)

 Test Files  1 failed | 13 passed (14)
      Tests  1 failed | 224 passed (225)
[kompactor: 224 passing-test lines hidden]
[kompactor: 7398›251 tokens; full output: kompactor show 1]

Both versions led to the right answer in all three runs. The Kompactor runs cost 37% less ($0.042 vs $0.066 median), and Claude got the diff and the exact failing line instead of a wall of checkmarks.

Measured results

Token-saving tools often quote huge percentages by counting raw output nobody would have read. We wanted numbers you can check, so Kompactor ships with kompactor prove. It runs each task in real headless Claude Code sessions (claude -p), three times without Kompactor and three times with it, interleaved, with the same prompt, tools, turn limit and budget cap. Tokens are input + cache writes + cache reads + output across every API call, and cost is Claude Code's own figure.

Nine tasks on five open-source repositories (Flask, Cobra, SWR, ky and zustand), 54 sessions in total, at the default Balanced level:

Change per task with Kompactor vs without (negative is better). Median of 3 runs each way.

TaskRepoTokensCostTurns (without › with)Correct
Find the failing Go test and its errorCobra−33%−18%3.3 › 2.03/3 › 3/3
Find how requests are deduplicatedSWR−32%−12%3.0 › 2.03/3 › 3/3
Find where the session cookie is signedFlask−26%−11%3.7 › 2.73/3 › 3/3
Find the flag-group validation functionCobra−24%−6%2.7 › 2.33/3 › 3/3
Most-changed files in the last 40 commitsFlask−16%+4%3.7 › 3.03/3 › 3/3
Find the failing vitest testzustand−7%−37%2.0 › 2.03/3 › 3/3
Type-check and list errorsky+1%−2%2.0 › 2.03/3 › 3/3
Find the failing pytest testsFlask+1%+3%2.0 › 2.03/3 › 3/3
What does changing createStore affectzustand+17%+4%2.0 › 2.33/3 › 3/3
Total (54 sessions)−16%−10%27/27 › 27/27

Three things stand out:

Every session's tokens, tool calls and answers are published: full report · raw results. You can rerun the whole benchmark yourself with kompactor prove.

Single commands: where the big cuts happen

Whole sessions shrink by 16% because most of each session is Claude Code's own fixed context: the system prompt, tool definitions and your messages. Look at single commands and the cuts are far larger. These are the tokens Claude receives for one command on a real repository (bench/bench.py in the repo):

Tokens Claude receives for one command (logarithmic scale, lower is better).

RepoCommandRawBalancedMax savings
Flaskpytest -v tests (1 failure, 482 passed)9,8421,138 (−88%)696 (−93%)
Flaskgrep -rn 'def ' src8,4923,230 (−62%)1,323 (−84%)
Flaskgit log -n 401,457598 (−59%)598 (−59%)
kynpm install (fresh)~33,000469 (−99%)451 (−99%)
kygrep -rn await source2,9381,653 (−44%)1,058 (−64%)
FlaskRead src/flask/app.py (1,625 lines) › outline14,0841,490 (−89%)1,490 (−89%)
kyRead source/core/Ky.ts (1,376 lines) › outline12,452919 (−93%)919 (−93%)

Quality vs tokens: five levels

One setting controls everything, from 1 Max quality (only outputs over ~1,500 tokens are touched, and only pure noise is removed) to 5 Max savings (80-line caps, one line of diff context, telegraphic replies). The dashboard shows a live preview of exactly what Claude would receive for realistic Python, Go, React and TypeScript output at each level, with the removed lines highlighted.

Kompactor dashboard, Quality vs tokens page: five levels from Max quality to Max savings, with a live preview of pytest output showing passing-test lines marked as removed
The Quality vs tokens page. At Balanced, the sample pytest run drops from 1.7k to 394 tokens; the red lines are what Claude no longer has to read.
LevelWhat Claude getsRules added
1 Max qualityAlmost everything. Only very large outputs are touched, and only pure noise is removed.0 tokens
2 QualityNoisy commands compacted, passing tests hidden, one-line git log, outlines for files over 800 lines.72 tokens
3 Balanced (default)Outlines from 400 lines, library frames trimmed, a 500-token code map, lean-code rules.156 tokens
4 SavingsTighter caps, 3 lines of diff context, outlines from 250 lines, shorter prose.176 tokens
5 Max savings80-line caps, 1 line of diff context, outlines from 150 lines, telegraphic prose.177 tokens

We ran the same 27 sessions at both ends of the range. Max quality is close to neutral, and Balanced and Max savings land within run-to-run noise of each other at the session level, because turns saved matter more than how hard each output is squeezed. Every level kept all 27 answers correct.

Change vs no Kompactor, by level (negative is better). 27/27 answers correct at every level.

Seeing what every turn saved

Kompactor shows its work in four places, so you never have to trust a single headline number:

Kompactor dashboard overview: 70.8k tokens kept out of context, 44% less tool output, a bar chart of tokens saved per turn, and a table of the commands whose output shrank most
The Overview page: tokens kept out of Claude's context, turn by turn, and the commands whose output shrank the most.

The numbers are deliberately conservative. Savings count only what Claude would really have read: when Claude Code would have truncated an output anyway, only the truncated size counts, and when Claude reads a whole file after its outline, the outline's saving is reversed. kompactor estimate goes further and replays your past Claude Code sessions through the current settings, so you can see what Kompactor would have saved on your own work before you rely on it.

The local code graph

The outlines and code map come from a local index of your repository: functions, classes, imports and calls for Python, Go, TypeScript, JavaScript and React. It builds in about a second (Flask: 85 files in 0.33 s), refreshes in the background after edits, and is never sent anywhere or written into your repo.

Kompactor code graph for Flask: 85 files, 1.6k symbols, a 395-token code map instead of about 165.8k tokens of source, a module map, and a search for session cookie listing matching classes with line ranges
The code graph for Flask: a 395-token map instead of ~165.8k tokens of source, and a "session cookie" search that answers with file, line range and who uses each match.

Claude uses it on its own when it needs to find code or check what a change affects, and you can query it directly:

kompactor graph query "session cookie"     # find code by topic, with line ranges and who uses it
kompactor graph impact createStore         # what uses it, and which tests to run
kompactor graph explain Name               # one symbol: signature, callers, callees, tests
kompactor graph report                     # core modules, entry points, import cycles
kompactor graph unused                     # dead-code candidates

Install Kompactor

Claude Code plugin (recommended). In any Claude Code session, run these one at a time, then restart Claude Code:

/plugin marketplace add amarkum/kompactor
/plugin install stackcone@kompactor
/kompactor setup

The last step adds the status line and the shell prefix that compacts failing commands. A plugin can't set those itself, so this one command does it, and it never replaces a status line or shell prefix you already have.

pip, pipx or uv (macOS, Linux and Windows; Python 3.9+, no dependencies):

pipx install kompactor
kompactor setup

VS Code: download the .vsix from GitHub Releases, install it with Extensions › ⋯ › Install from VSIX…, and click Enable. The Claude Code CLI and the VS Code extension read the same settings, so one install covers both.

Using it day to day

Nothing changes in how you work. Ask Claude Code what you normally would, for example "run the tests and tell me which one fails", "where is the session cookie signed?" or "what does changing createStore affect?". To check it's running, type kompactor in a terminal:

Kompactor 0.7.6: ON
  Connected via the Claude Code plugin. New Claude Code sessions are compacted.
  Level: Balanced (level 3 of 5). Change it: `kompactor config preset 1-5`, the dashboard, or /kompactor balanced
  Saved so far: 41.3k tokens (−38%) over 52 tool calls. Details: `kompactor gain`
In Claude CodeWhat it does
/kompactorIs it on, your level, savings so far
/kompactor balanced, /kompactor max savings, /kompactor max qualityChange the level
/kompactor reviewSuggests cuts in your current diff: duplicated helpers, unused options, dead branches
/kompactor audit · /kompactor debtDead-code candidates · a list of FIXME/HACK/TODO shortcuts
/kompactor stop · /kompactor startPause it · turn it back on

To turn it off at any time: kompactor stop pauses it and keeps your settings, kompactor start resumes it, and kompactor uninstall removes it from Claude Code (add --purge to delete its local data too).

Rolling it out to a team

To offer Kompactor to everyone who works in a repository, commit this as .claude/settings.json. When a teammate trusts the folder, Claude Code installs the plugin, and each person runs /kompactor setup once:

{
  "extraKnownMarketplaces": {
    "kompactor": { "source": { "source": "github", "repo": "amarkum/kompactor" }, "autoUpdate": true }
  },
  "enabledPlugins": { "stackcone@kompactor": true }
}

To set the team's level for a repository, commit a .kompactor.json next to the code, for example to keep terraform plan output untouched:

{ "preset": 4, "overrides": { "never_compact": ["terraform plan"] } }

What it runs, and what it never does

A tool that sits between your agent and your code has to be easy to trust, so here is the complete list:

The short rules it adds to Claude's context are plain text you can read in rules.py. Full details are in the README and the privacy policy.

Limits and honest caveats

FAQ

Does Kompactor change Claude's answers?

In the measured runs, no: 27 of 27 answers were correct with and without it. It removes noise and always keeps errors, failures and summary lines, and Claude can fetch any original with kompactor show.

Does it send my code anywhere?

No. Everything happens locally through Claude Code's hooks.

Why is the per-session saving smaller than the per-command saving?

Because most of what each turn re-sends is Claude Code's own fixed context. Kompactor shrinks the tool-output part, which can drop 80 to 99% per command.

Is it free?

Yes. It's MIT-licensed and open source on GitHub, and available on PyPI.

Something looks wrong. What do I do?

Run kompactor doctor, or kompactor stop to turn it off at once. To run a single command untouched, start it with KOMPACTOR_RAW=1. Bugs and ideas go to GitHub issues.

Running Claude Code across a team?

stackcone builds production agent loops, Claude Code plugins, MCP integrations and custom coding harnesses, and can roll Kompactor out with your team's settings.