Long test and build output in Claude Code: why it burns context and how to cut it
Downshift by Tiago Vilas Boas. Updated
Downshift here is a Go tool for AI coding agents, not the React downshift autocomplete library and unrelated to any car transmission.
TL;DR
Every command result Claude Code reads becomes part of the conversation and goes out again with each later request. A passing suite prints a line per package or test that says nothing a single PASS line would not. Run fewer tests, use quiet flags, treat | tail with care, and keep failures whole. downshift compact automates the last part: it shortens only clean passing output, leaves failures byte for byte and returns the real exit code.
Why does long test output burn context in Claude Code?
A green test run is a stack of identical receipts. Each line says the same thing, passed, and Claude Code keeps the whole stack in its pocket for the rest of the session.
That is how the context works. When Claude runs a command through the Bash tool, the output enters the conversation as a tool result. Anthropic's cost page says Claude Code sends the full conversation with every request, and each tool call sends another request carrying its results. Prompt caching re-reads that history at a cheaper rate, but it is still read, it still fills the context window, and it brings auto-compaction closer.
Passing output is mostly repetition: one line per package with go test, one line or dot per test with most runners, timings and a summary. The model needs one fact from it: everything passed, with exit code 0. A failing run is the opposite. The assertion, the diff and the stack trace are the information, so a failure must stay whole.
So the rule for the rest of this guide is simple: shrink the success, never the failure.
How do I reduce test output tokens without installing anything?
Start with what Claude Code and your test runner already give you.
| Lever | What it cuts | The catch |
|---|---|---|
| Run focused tests | go test ./pkg/ -run TestName, one pytest node, one spec file instead of the whole suite | You still need the full suite before you merge |
| Quiet flags | go test without -v, pytest -q, a dot or summary reporter | Some quiet modes also shorten failure output; check what a failure looks like first |
| tail -N | Keeps only the last N lines | The pipe returns tail's exit code, and a failure printed above the last N lines is gone |
| Delegate the run to a subagent | Verbose output stays in the subagent's context; only a summary comes back (sub-agents docs) | The subagent's own requests still draw on your usage |
| /compact and /clear | /compact summarizes the conversation; /clear starts fresh at no cost | Both act after the fact. /compact itself reads the whole context, and the next long run fills it again |
| A hook that filters output | Anthropic's cost page shows a PreToolUse hook that rewrites test commands to show only failure lines | You own the script, and its grep and head pipe decides which lines and which exit code Claude sees |
The | tail catch deserves a closer look, because agents reach for it constantly. In bash and zsh a pipeline reports the exit status of its last command, so a failing suite piped to tail looks green unless pipefail is on:
go test ./... | tail -5 # status is tail's, usually 0
set -o pipefail; go test ./... | tail -5 # status is go test's when it failsNone of these levers knows the difference between a pass and a failure. They cut both, or they cut neither.
What does downshift compact do to test output?
Downshift is an open-source Go binary that runs as hooks inside coding agents. Its Compactor is one command: downshift compact -- <command>. It runs your command and looks at the result before the agent does.
- Exit 0, empty stderr and output it recognizes: the output is replaced by a short PASS summary plus a marker line that says how many bytes were cut.
- A failure, a non-zero exit or output it does not recognize: passed through byte for byte.
- The exit code is always the command's own.
It works on an allowlist: go test, pytest, vitest, jest, tsc, cargo test, phpunit, playwright test, cypress run, gradle, maven, flutter test and more. Any other command, including npm test, is not wrapped. The exact forms are in the harness matrix. To switch it off, set "exec": false in the Downshift config or DOWNSHIFT_EXEC=0 in the environment.
What does it look like on a real run?
Single run, 2026-10-24, on the Downshift repository: three Go packages, plain go test without -v, macOS.
Before: raw output, 202 bytes
$ go test ./internal/execwrap/ ./internal/telemetry/ ./internal/classify/
ok github.com/tiagovilasboas/downshift/internal/execwrap 26.559s
ok github.com/tiagovilasboas/downshift/internal/telemetry 1.732s
ok github.com/tiagovilasboas/downshift/internal/classify (cached)After: downshift compact, 167 bytes including the marker line
$ downshift compact -- go test ./internal/execwrap/ ./internal/telemetry/ ./internal/classify/
Go test: PASS (3 packages ok) under github.com/tiagovilasboas/downshift/internal/
ok execwrap
ok telemetry
ok classify
[downshift compact: compacted 205 to 119 bytes]202 to 167 bytes, about 17% fewer, on output that was already short. The marker line counts its own run (205 to 119 bytes before the marker is added); package timings differ between the two runs. Plain go test is terse by design. The larger cuts come from runners that print a line per test, verbose flags and build tools.
Source: the two commands above, run once on 2026-10-24 with downshift v0.1.2-65 (commit 480391dc). Byte counts by wc -c. Your output will differ.
Does Claude Code rewrite test commands automatically?
On Claude Code, yes. Downshift's PreToolUse hook sees the Bash command before it runs and rewrites allowlisted commands into the compact form, including the shapes agents type most often:
| Command the agent writes | What runs |
|---|---|
cd api && go test ./... | cd api && downshift compact -- go test ./... |
go test ./... 2>&1 | tail -20 | downshift compact -- go test ./... 2>&1 | tail -20 |
go build ./... && go test ./... | each segment wrapped; && still stops at the first failure |
gofmt -l .; go test ./pkg | only the go test segment wrapped |
A deny or ask rule that matches the command, or any part of it, stops the rewrite. A command that already holds downshift compact is never wrapped twice. With | tail -N the pipe still reports tail's status, exactly as before; the rewrite does not change exit semantics.
Other harnesses, such as Codex, Cursor, OpenCode, Antigravity and KiroCrew, have no channel to rewrite a shell command in place. There the agent runs downshift compact -- <command> itself. It is a plain shell command, so it behaves the same in every harness and on every OS.
How much does output compaction save?
The one measurement is a byte count on a synthetic corpus, dated 2026-10-21: 248 fixture files of command output, of which 26 compact. On those 26 clean passing outputs, 82.3% fewer bytes. Across all 248, 23.7% fewer, because most fixtures are failures and edge cases built to stay raw. These are bytes, not LLM tokens, measured on fixtures, not real sessions. Your mix of commands will differ. Read the corpus report.
Frequently asked questions
Does compacting test output hide failures from Claude?
No. Compaction runs only when the command exits 0 with empty stderr and the output is recognized as a pass. A failure, a non-zero exit or unknown output passes through byte for byte.
Does downshift compact change the exit code?
No. It returns the command's own exit code. In a pipe such as | tail -N the shell still reports the last command's status, the same as without Downshift.
Is piping to tail enough?
It cuts size, but the pipe returns tail's exit code unless pipefail is set, and a failure printed above the last lines is lost. It also trims failing output, which is the part the model needs.
Does it work outside Claude Code?
Yes, as a command. Only Claude Code rewrites commands automatically through the hook. In Codex, Cursor and other harnesses the agent runs downshift compact -- <command> directly.
How do I turn it off?
Set "exec": false in the Downshift config or DOWNSHIFT_EXEC=0 in the environment. A deny or ask rule that matches a command also stops its rewrite.
Stop paying context for green checkmarks
Install Downshift, preview every change with downshift setup --dry-run, and the next passing suite comes back as one summary line while failures stay whole. downshift setup --undo restores your backups.
curl -fsSL https://raw.githubusercontent.com/tiagovilasboas/downshift/main/install.sh | sh && downshift setupClaude Code plugin
/plugin install downshift --marketplace tiagovilasboas/downshiftRelated guides
Sources
- Anthropic docsmeasured
code.claude.com/docs/en/costs
full conversation sent with every request, /compact reads the context, /clear, delegating verbose operations, test-output hook example (fetched 2026-10-24) - Anthropic docsmeasured
code.claude.com/docs/en/sub-agents
isolating high-volume operations in a subagent - Anthropic docsmeasured
code.claude.com/docs/en/hooks
PreToolUse hooks - Downshift repomeasured
docs/evidence/compaction-corpus-2026-10-21.md
248 fixtures, 26 compacted, 82.3% and 23.7%, bytes not tokens - Downshift repomeasured
docs/reference/harness-matrix.md
allowlist, compound shapes and chains, rewrite only on Claude Code - Downshift repomeasured
docs/features/compaction.md
compacts only on exit 0 with empty stderr; failures preserved - Downshift CLI outputmeasured
go test and downshift compact, single run on 2026-10-24
before and after byte counts