Repository navigation
Claude Code → Responses translation keeps the per-request billing header, so Muse/OpenAI prompt cache never hits #6627
Description
Client or integration
Claude Code
Area
Proxy and routing
Summary
Claude Code requests translated to a Responses upstream get essentially no prompt-cache hits. I saw this with meta-muse (Muse Spark 1.3 Contributor). The translated instructions start with Claude Code's per-request billing line:
x-anthropic-billing-header: cc_version=…; cc_entrypoint=…; cch=…;
cch changes on every request. So the first bytes of the prompt differ every turn, and a prefix cache never matches. Anthropic excludes this line from caching; Muse and OpenAI do not. src/adapters/google.ts already strips it (stripAntigravityBillingHeader). systemToInstructions in src/claude/inbound-content-options.ts does not, and it passes the line through to every translated route. That function is unchanged on dev at 0511f458f1.
The system-derived prompt_cache_key fallback hashes the same system text, so it also rotates per request when a client sends no metadata.user_id.
Reproduction
- Route a Claude Code subagent (Desktop entrypoint, first-party mode) to
meta-muse/muse-spark-1.3-contributor, directly or through a combo. - Run a 5-turn task, where each turn is one tool call.
- With
OPENCODEX_CACHE_DEBUG=1,cache-debug.jsonlshows:prompt_cache_keyis present and equal on every turn.- The tools and the later instruction and message blocks keep stable tags.
- Only the first instruction block's tag changes on every turn.
- Upstream
cached_tokensis 0 on every turn.
- Control: send
/v1/responsesdirectly with a stable ~15k-tokeninstructionsand a fixedprompt_cache_key. Turns 2–3 cache about 99%. That rules out the account, the key and non-ASCII content.
claudeCode.stabilizePromptCache: true does not help, because it only relocates trailing footers.
Verified fix. Strip a leading x-anthropic-billing-header: line in systemToInstructions, matching only at the prompt start, with the same regex as the Google adapter. Apply it to the string itself for a string system, and to the first text block only for an array system, dropping the block if it ends up empty:
function stripClaudeBillingHeader(text: string): string {
return text.replace(/^x-anthropic-billing-header:[^\n]*\n*/, "");
}With that patch, the same 5-turn subagent went from 0 cached on every turn to 21,361, 22,129, 22,257 and 22,385 cached on turns 2–5 (about 99%), on two machines. Native Anthropic passthrough doesn't use this translation and is unaffected.
Version
opencodex 2.76.0, Claude Code 2.1.286
Operating system
macOS
Provider and model
meta-muse / muse-spark-1.3-contributor (also reached through a failover combo)
Logs or error output
usage.jsonl, same Muse account:
- Codex (
inboundProtocol: responses): 96.5% of input cached, over 63,895 requests. - Claude Code (
inboundProtocol: messages): 0.7% cached, over 15,907 requests, every day since first use.
Redacted configuration
claudeCode.desktopMode: first-party; meta-muse provider adapter: openai-responses, baseUrl: https://api.meta.ai/v1.
Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Activity
github-actions commentedon Oct 5, 2026
Issue reopened
The report now contains the information required by the automated check. Thanks for updating it.
fix(claude): strip Claude Code's rotating billing line before Respons…
lidge-jun commentedon Oct 5, 2026
Fixed on
devby #6634 (0889b60): the Messages→Responses translation now strips Claude Code's leadingx-anthropic-billing-headerline before it becomesinstructions, so the translated prefix and the system-derived fallbackprompt_cache_keystay stable across turns. Thanks for the precise diagnosis and the verified patch. Closing manually because PRs targetdev; it ships in the next release.