Skip to content

Strip the billing header wherever it appears, not just line 1 - #180

Merged
teamchong merged 1 commit into
mainfrom
fix/billing-header-out-of-slab
Aug 6, 2026
Merged

Strip the billing header wherever it appears, not just line 1#180
teamchong merged 1 commit into
mainfrom
fix/billing-header-out-of-slab

Conversation

@teamchong

Copy link
Copy Markdown
Owner

Fixes #177.

Claude Code 2.1.222 injects a per-turn header into the system text:

x-anthropic-billing-header: cc_version=2.1.222; cc_prev_req=req_011Cdk3...

cc_prev_req names the previous request, so the line changes every turn. It has to be relocated out of the cached slab and re-appended as volatile tail text, which is what stripBillingLine is for.

The old matcher only tested the first line. The header is not always first, it lands after other system content depending on what else the CLI prepends. When it isn't first it stays in the slab, the rendered prefix differs by a few bytes every turn, and the prompt cache misses on essentially every request.

Before:

// only matched at position 0
const m = /^x-anthropic-billing-header:[^\n]*/.exec(text);

After:

// matches at line start anywhere; m[0] swallows the preceding \n
const m = /(?:^|\n)x-anthropic-billing-header:[^\n]*/.exec(text);

The header then joins sysTail alongside the other per-turn churn, and the slab goes back to being byte-identical across consecutive turns.

Note this changes slab bytes, so it invalidates existing warm caches once on deploy.

Claude Code sends x-anthropic-billing-header as its own system block, so
after extractSystemText joins the blocks it is never line 1 and the old
first-line-only test never fired. The line fell through to
splitStaticDynamic, which has no XML wrapper to key on, classified it
static, and rendered it into the slab PNG.

On CLI 2.1.220 the value was session-stable (11/11 captured bodies share
cch=07295), so the PNG stayed byte-identical and nothing showed. 2.1.222
added cc_prev_req=<previous request id>, making the line per-turn by
construction: the slab re-renders and the whole cached prefix is void on
every request.

Match the line wherever it appears, so it re-enters the system tail
beside dynamicText/envMarkdown, outside the image.
@teamchong
teamchong force-pushed the fix/billing-header-out-of-slab branch from 28be574 to 50b80fa Compare August 5, 2026 18:43
@teamchong
teamchong merged commit 2a071a8 into main Aug 6, 2026
6 checks passed
@teamchong
teamchong deleted the fix/billing-header-out-of-slab branch August 6, 2026 02:18
teamchong added a commit that referenced this pull request Aug 6, 2026
Cache-correctness release. The per-turn billing header no longer lands in
the cached prefix (#180, #161), plus tag-scanning, history-collapse and
supply-chain hardening.

Relabel the stale 'Unreleased' heading to 0.11.0: its contents (#146,
08ec156) shipped in v0.11.0 and v0.11.1 without a changelog entry.
teamchong added a commit that referenced this pull request Aug 9, 2026
The #206 fix appended x-anthropic-billing-header after the final user
message's cache markers. Cache-safe, but the block renders as
user-attributed conversation text, so the line leaked into the
transcript every turn.

There is no body position that is both cache-safe and invisible:
req.system precedes the last cache_control marker (#180/#161), and
anything after the markers reads as the user's words. So the line now
leaves the body entirely: transform exports info.billingLine and the
proxy sets it as an actual HTTP header on the upstream request.

Tests now assert the line appears nowhere in the outgoing body, from
any source position, instead of pinning a body placement.
teamchong added a commit to SiNaPsEr0x/pxpipe that referenced this pull request Aug 9, 2026
The CLI now emits per-request cch/cc_prev_req values in
x-anthropic-billing-header. Re-emitting the line in system placed it
before the last cache_control marker, so every turn produced a distinct
cached prefix: 0 cache reads, full input price (teamchong#180).

Re-emit it instead as the final un-cached block of the last user
message, past all markers. Cached span is now byte-identical across
turns.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Prompt cache broken almost every turn: per-turn billing header re-injected into system, assumed session-stable

1 participant