Headroom barely moves Claude Code subscription usage — and needs one or two proxies in VS Code
Headroom compresses Claude Code requests in a local proxy. On 2.1.292 it moved subscription usage by under 5% either way, and cut API pricing by 4 to 16%.

On this page
Introduction
I heard that Headroom could cut Claude Code’s token usage, and I wanted to know how it works before deciding whether to adopt it. My Claude Code runs as the VS Code extension on a subscription, with claude.ai connectors attached, so anything placed in front of the API had to leave those alone.
Headroom compresses requests in a local proxy, and there are two ways to put that proxy between Claude Code and the API. Pointing ANTHROPIC_BASE_URL at it switches off ToolSearch and Remote Control and inflates the context meter. A forward proxy that intercepts the connection to api.anthropic.com instead kept every feature I could measure, but takes two proxies.
On my subscription, usage barely moved. It landed between 5% less and 5% more. Most of what Headroom compresses is cached, and cached tokens count for little against a subscription’s limits. At API prices the same runs cost 4 to 16% less, but the costs of either route far outweigh that, so I am not adopting it.
This article covers both routes and when the trade is worth making.
How Headroom sits in the request path
Headroom is an open-source compression layer for coding agents, published as the Python package headroom-ai. It shrinks what goes into a model request (tool schemas, tool output, logs and conversation history) and keeps the originals on disk, so the model can fetch one back through a headroom_retrieve tool.
It has to sit in the network path, because nothing inside Claude Code can rewrite a request. A hook is a command Claude Code runs automatically at set points. Hooks such as PreToolUse, PostToolUse and Stop run around tool calls and turns, and none of them receives the API request body.
That leaves two ways in. Route 1 replaces the endpoint Claude Code sends to, which is what Headroom’s own setup commands do:
headroom proxy --port 8787ANTHROPIC_BASE_URL=http://127.0.0.1:8787 claudeRoute 2 leaves the endpoint alone and intercepts the connection on its way out. It needs a second tool, covered in its own section below.
On route 1 the proxy forwards /v1/messages, the API path every model call goes to, to api.anthropic.com. Subscription sign-in passes through unchanged. A proxied session reported apiKeySource: none, the same as a direct one. That means it used no API key and stayed signed in through the subscription account. Every request succeeded.
Route 1: replacing ANTHROPIC_BASE_URL
Claude Code checks whether ANTHROPIC_BASE_URL points at api.anthropic.com and turns some features off when it does not. I asked Claude Code to start the same headless session, run with claude -p and no interactive interface, twice on version 2.1.292, once direct and once through the proxy, and compare the tools and commands each one received.
| Direct | Through the proxy | |
|---|---|---|
| claude.ai connectors | 13 | 13 (12 of 13 runs) |
/schedule | available | available |
ToolSearch tool | available | missing |
Artifact tool | available | missing |
| Remote Control | available | refused |
Remote Control refuses to start behind the proxy, and says why:
INANTHROPIC_BASE_URL=http://127.0.0.1:8787 claude remote-controlOUTError: Remote Control is only available when using Claude via api.anthropic.com. ANTHROPIC_BASE_URL is set and does not point at api.anthropic.com, so this session is using a custom endpoint — unset it (or run in a shell without it) to use Remote Control.ToolSearch is the tool that loads MCP tool definitions on demand instead of sending every one of them with every request. Setting ENABLE_TOOL_SEARCH=true brings it back behind the proxy; the Artifact tool stays missing.
/schedule was in every proxied session, and all 13 connectors loaded in 12 of 13 proxied runs. The thirteenth came up with no claude.ai connectors at all, which never happened in the 8 direct runs.
/context misreports usage when the base URL points at the proxy
/context prints how much of the context window a session is using. Behind the proxy, that number more than tripled:
| Arm | /context total | MCP tool definitions |
|---|---|---|
| Direct | 47.4k | 107.7k deferred, not counted |
| Proxy | 156.1k | 109.1k loaded |
Proxy with ENABLE_TOOL_SEARCH=true | 191.5k | 137.9k loaded |
Deferring those definitions is ToolSearch’s job. With the base-URL check switching it off, Claude Code counts every MCP tool definition as loaded up front. It also counts them before Headroom compresses anything, so the model receives fewer tokens than the meter reports. Setting ENABLE_TOOL_SEARCH=true did not bring the deferred row back.
Billing follows what the model receives, so the inflated number costs nothing, and auto-compaction ignores it too. With the auto-compact window set to 120k, so the displayed usage crossed the threshold, two messages in a row triggered no compaction.
Routing the VS Code extension with wrap vscode-claude
headroom wrap claude starts the proxy and launches the Claude Code CLI with ANTHROPIC_BASE_URL set on that one process, so it covers a terminal session and nothing else. The VS Code extension starts its own Claude Code binary from VS Code’s environment, and Headroom has a separate subcommand for it. headroom wrap vscode-claude --help prints:
Usage: headroom wrap vscode-claude [OPTIONS]
Route VS Code's official Claude Code extension through Headroom.
Run this from your project, reload VS Code after first setup, and keep this command running while using Claude Code. Authentication and model selection remain unchanged. Run `headroom unwrap vscode-claude` to restore settings.The wrapper writes two entries into Claude Code’s user settings.json: ANTHROPIC_BASE_URL set to http://127.0.0.1:8787/p/<project>, and ENABLE_TOOL_SEARCH set to "true". User settings apply to every Claude Code session on the machine, so while that file carries the entry, every VS Code window and every terminal session goes through the proxy, whichever project the command ran in.
Headroom’s documentation says a stopped proxy does not fall back: “A stopped proxy intentionally does not fall back to a direct Anthropic connection.” So if the wrapper process stops, every one of those sessions loses its connection.
Route 2: a forward proxy that leaves the base URL alone
A forward proxy is one a client hands all its outbound connections to, configured through https_proxy. claude-rc-proxy, shared in a comment on a Headroom issue, is one built for this. It decrypts only connections to api.anthropic.com, and passes every other host through as a raw, unread tunnel.
Inside that connection it sends only /v1/messages requests to a local backend. Everything else, including Remote Control’s bridge and the OAuth calls, goes to the real Anthropic unchanged. ANTHROPIC_BASE_URL stays unset, so the first-party check passes.
Claude Code read the source before anything ran, and found that as published, it does not work with Headroom. It is built for a model gateway, so it replaces the request’s Authorization header with a gateway token and sends /v1/messages requests to the gateway at 127.0.0.1:8317.
Pairing it with Headroom took a local fork that points it at Headroom’s port and leaves the caller’s own token in place:
upstreamPool = "127.0.0.1:8317" // CLIProxyAPIupstreamPool = "127.0.0.1:8787" // Headroom// …r.URL.Scheme, r.URL.Host = poolURL.Scheme, poolURL.Hostr.Header.Set("Authorization", "Bearer "+poolToken)The fork also drops the guard that refuses /v1/messages requests when no gateway token is set.
For rc-proxy to decrypt the connection, Claude Code has to trust the certificates it signs, so I made a certificate authority for the test (the root certificate that signs TLS certificates) and had only the Claude Code process trust it, through NODE_EXTRA_CA_CERTS. ANTHROPIC_BASE_URL stayed unset:
export https_proxy=http://127.0.0.1:9801 http_proxy=http://127.0.0.1:9801export no_proxy=localhost,127.0.0.1,::1,datadoghq.com,statsig.com,sentry.ioexport NODE_EXTRA_CA_CERTS=/tmp/rcproxy/ca/ca-cert.pemI compared a direct session with one through both proxies, from the same shell:
| Direct | Route 2 | |
|---|---|---|
| claude.ai connectors | 13 in 8 of 8 runs | 13 in 9 of 9 runs |
/schedule | available | available |
ToolSearch tool | available | available |
/context total | 32.3k | 33.7k |
apiKeySource | none | none |
Nothing I could measure was lost. The MCP tool definitions stayed deferred, so the meter reads what a direct session reads. Headroom’s log showed 12 /v1/messages requests reaching it with 0 failures, so inference really did go through it.
What the requests saved
I tested route 1 and route 2 in separate batches. Each batch ran several connection setups, called arms below, interleaved so they shared the same few minutes. Route 1 had three arms: direct, the base URL pointed at the proxy, and that plus ENABLE_TOOL_SEARCH=true. Route 2 had two: direct, and through both proxies.
Claude Code ran 21 requests for route 1 from its own shell, and I ran 16 for route 2 from my terminal. One prompt asks for a one-line reply. The other runs git log --stat -40 and reads the output back. Every answer matched across arms.
Each run used one command, and its last line of output carries the usage. The two routes differ only in their environment variables:
ANTHROPIC_BASE_URL=http://127.0.0.1:8787 claude -p "Reply with exactly: OK" \ --output-format stream-json --verbosehttps_proxy=http://127.0.0.1:9801 NODE_EXTRA_CA_CERTS=/tmp/rcproxy/ca/ca-cert.pem \ claude -p "Reply with exactly: OK" --output-format stream-json --verboseWhat counts toward a subscription’s usage limit
A subscription has no per-token bill, so what matters is how fast a session uses up its limits. Anthropic’s usage-limit guidance says cached portions of a prompt “count less against your limits than new content”, and publishes no weighting. A third-party tracker of subscription accounts estimates from a regression that a cache read uses “close to none” of the limit.
Test 1: one-line prompt and git log --stat
Taking that at face value, the number to watch on a subscription is the uncached input plus the cache writes, with cache reads left out:
| Prompt | Route | Direct | Through Headroom | Change |
|---|---|---|---|---|
| One-line | Route 1, ToolSearch missing | 16,245 | 11,220 | −31% |
| One-line | Route 1, ToolSearch restored | 16,245 | 16,993 | +5% |
git log --stat | Route 1, ToolSearch restored | 18,434 | 17,779 | −4% |
| One-line | Route 2 | 13,636 | 12,989 | −5% |
git log --stat | Route 2 | 17,271 | 17,118 | −1% |
With ToolSearch kept, subscription usage moved between 5% less and 5% more, which is inside the spread between runs of the same arm. The route 1 direct arm alone ranged from 14,090 to 17,452 on the one-line prompt.
The one large cut, 31%, is the route 1 arm that lost ToolSearch. It comes from loading fewer tool definitions, not from compression.
Test 2: an eight-turn session with heavy tool output
One-line prompts are mostly the fixed prefix, so I also ran a workload closer to real use. Each session has eight turns, and each turn reads a large file or runs a command with long output. Every arm ran twice, interleaved, from my terminal.
| Arm | Round 1 | Round 2 | Mean vs direct |
|---|---|---|---|
| Direct | 40.9k | 44.8k | — |
Route 1, ToolSearch restored | 34.0k | 37.2k | −17% |
| Route 2 | 46.1k | 39.1k | about 0% |
The numbers are subscription usage, uncached input plus cache writes, summed over turns 2 to 8. The first turn writes the session’s cached prefix, and its size swung from 16k to 57k depending on whether the cache was still warm, so it is left out.
Headroom left the tool output almost untouched. Across the 76 requests it reported, it shrank message content by 1.9%. Most of its 12% headline saving was tool schemas, 344,690 tokens that sit in the cached prefix, so it saved little that counts against the limit.
Route 1’s 17% is two runs against two, and route 2 sent the same requests through the same Headroom and saved almost nothing, so I read it as run-to-run noise rather than compression.
What API pricing would see
On API pricing, cache reads still cost a tenth of the input rate, so compressing the cached prefix saves something. Below are Test 1’s results repriced at API rates.
Total input counts every token the model received. Weighted cost prices them the way a cached prefix is billed, with cache writes at 1.25 times the base input rate and cache reads at 0.1 times.
Route 1:
| Prompt | Arm | Runs | Total input | Weighted cost |
|---|---|---|---|---|
| One-line | Direct | 5 | 59,218 | 24,603 |
| One-line | Proxy, ToolSearch missing | 5 | 35,963 | 16,499 |
| One-line | Proxy, ToolSearch restored | 5 | 40,825 | 23,727 |
git log --stat | Direct | 3 | 121,077 | 39,338 |
git log --stat | Proxy, ToolSearch restored | 3 | 113,323 | 33,154 |
Route 2:
| Prompt | Arm | Runs | Total input | Weighted cost |
|---|---|---|---|---|
| One-line | Direct | 5 | 44,550 | 20,153 |
| One-line | Route 2 | 5 | 37,985 | 18,735 |
git log --stat | Direct | 3 | 94,313 | 29,100 |
git log --stat | Route 2 | 3 | 81,255 | 27,822 |
Each figure is the median across that arm’s runs. The two direct baselines differ because the batches ran in different shells with different tools loaded, so compare each route only with the direct arm beside it. The route 1 arm without ToolSearch looks cheapest at 33% off, and it pays for that with the missing tool.
Most of the token cut lands on the cached prefix, which is already billed at a tenth of the price. On the one-line prompt, route 1 with ToolSearch restored cut tokens by 31% and cost by 4%. Route 2 cut tokens by 15% and cost by 7%.
Headroom’s own report said 36.5% for the route 1 batch and 15.0% for the route 2 batch. But those figures count tokens alone, with no allowance for cached tokens being cheaper.
Why I do not recommend Headroom
On a subscription, Headroom moved usage by less than 5% either way, and both routes cost something, so I do not recommend adopting it.
Route 1 costs features:
- Remote Control refuses to start.
ToolSearchdisappears unlessENABLE_TOOL_SEARCH=trueis set, and theArtifacttool disappears regardless.- The context meter reads three to four times the size of the request the model receives.
- The claude.ai connectors failed to attach in one of 13 proxied runs.
Route 2 keeps them, and costs trust instead:
- A local process decrypts every connection to
api.anthropic.com, OAuth token included. - Pairing it with Headroom takes a fork of a third-party tool, which I would then maintain.
- It stops working the day Anthropic pins its certificates, meaning Claude Code accepts only Anthropic’s own.
On either route, every Claude Code session on the machine depends on local proxies staying up, and route 2 puts two of them in the chain.
Headroom’s README claims 60–95% for JSON, but that case rarely arises in Claude Code. I had Claude Code run 36 commands that print JSON, and in 31 of them it filtered the output through jq itself, so the raw JSON never entered the context. Across my past 195 sessions, JSON was also only about 2% of tool output.
Summary
- No Claude Code hook can rewrite the API request body, so Headroom compresses requests in a local proxy.
- Route 1 replaces
ANTHROPIC_BASE_URL. On 2.1.292 it switches off Remote Control,ToolSearchand theArtifacttool, and inflates/context.ENABLE_TOOL_SEARCH=truerestoresToolSearch, but theArtifacttool stays unavailable. - Route 2 is a forward proxy such as claude-rc-proxy. It keeps connectors,
/scheduleandToolSearch, and/contextreports the right number, but it means forking and maintaining a third-party tool. - On a subscription, with
ToolSearchkept, usage moved by under 5% either way, in one-line prompts and in a session full of tool output, and most of the token cut lands on the cached prefix, which counts for almost nothing against the limits. At API prices it comes to roughly 4 to 16% less. - Claude Code usually filters JSON through
jqfirst, so the README’s 60–95% rarely comes into play. Neither route delivers a real cut in usage, so I do not recommend adopting Headroom.
Testing the output of claude -p --output-format stream-json is enough to compare before deciding. The init line lists which tools the session has, and the usage line lists the tokens spent. Try it yourself.
References
- headroomlabs-ai/headroom
- Headroom CLI reference
- Use Headroom with Claude Code in VS Code
- Remote Control disabled behind the proxy (headroomlabs-ai/headroom#1779)
- Comment: use a forward proxy that leaves ANTHROPIC_BASE_URL unset
- claude-rc-proxy, in dthinkr/claude-code-workarounds
- Connect Claude Code to tools via MCP, including ENABLE_TOOL_SEARCH
- Prompt caching pricing for cache writes and reads
- Usage limit best practices, on how cached content counts against subscription limits
- Claude usage limits measured on subscription accounts, including cache reads

