From cbe151f3bca59c1ac1e62f69c160236290df53d6 Mon Sep 17 00:00:00 2001 From: icebear0828 Date: Sat, 16 May 2026 12:44:44 -0700 Subject: [PATCH] test(translation): cover Claude Code 2.1.84 billing-header rotation variants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three batches of live samples (~50 /v1/messages ingress) confirm Claude Code 2.1.84 always emits x-anthropic-billing-header as a standalone system[0] block with rotating cc_version (3-hex suffix) and cch (5-hex token) per request. The existing prefix-startsWith strip handles this shape correctly, but only a single fixture exercised it. A future refactor — swapping the prefix check for a regex, adding inline cleanup, etc. — could silently regress and leak cch back into the flattened instructions, defeating the cache-buster guard that inspired the strip in the first place. Add an it.each fixture across five observed cc_version suffixes (c8e / 76b / f51 / 5b4 / 4f3) plus an explicit invariance assertion that two different billing-header values produce byte-identical instructions. Fixture mirrors the real 3-block shape including cache_control ephemeral markers on the prompt blocks. No production code change. --- CHANGELOG.md | 1 + .../translation/anthropic-to-codex.test.ts | 60 +++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26130d94..66c4dc8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ ### Fixed +- Claude Code 2.1.84 计费头 strip 行为新增 rotation 变体回归覆盖:实测 Claude Code 把 `x-anthropic-billing-header: cc_version=...; cc_entrypoint=cli; cch=<5hex>;` 作为 `system[0]` 独立块下发,`cc_version` 后缀和 `cch` 每请求 reroll;新增 `it.each` 覆盖 5 个真实 `cc_version` 后缀(c8e / 76b / f51 / 5b4 / 4f3)与"两次不同 cch 产出同一份 `instructions`"的不变性断言,防止后续改 strip(如改 `startsWith` → 正则、或加 inline 清洗)意外让 `cch` 漏进 `instructions` 污染上游 prompt cache(`tests/unit/translation/anthropic-to-codex.test.ts`) - `/v1/responses` passthrough streaming / non-streaming paths now collect `function_call.call_id` from `response.output_item.done` and forward it through response metadata so implicit resume can validate following `function_call_output` turns instead of falling back to full-history replay. Oversized missing-tool-call replays are guarded with 413, and regression coverage now proves the issue red/green across the Responses format adapter (`src/routes/responses.ts`, `src/routes/shared/proxy-handler.ts`, `tests/unit/routes/responses-passthrough-metadata.test.ts`, `tests/integration/proxy-handler.test.ts`). - Release bump workflows now require runtime file changes in addition to meaningful commit subjects before tagging a beta or stable build. This prevents squash-promotion history divergence from re-counting old dev commits, and prevents workflow/docs/test-only fixes from producing empty Electron releases (`.github/workflows/bump-electron.yml`, `.github/workflows/bump-electron-beta.yml`, `tests/unit/ci/package-boundary.test.ts`). - Release bump workflows now skip the release-notes workflow hotfix subject itself, so promoting the stable-notes CI fix to `master` does not create an empty desktop release on the next scheduled bump (`.github/workflows/bump-electron.yml`, `.github/workflows/bump-electron-beta.yml`, `tests/unit/ci/package-boundary.test.ts`). diff --git a/tests/unit/translation/anthropic-to-codex.test.ts b/tests/unit/translation/anthropic-to-codex.test.ts index bf94d380..1aeb1a1e 100644 --- a/tests/unit/translation/anthropic-to-codex.test.ts +++ b/tests/unit/translation/anthropic-to-codex.test.ts @@ -106,6 +106,66 @@ describe("translateAnthropicToCodexRequest", () => { expect(result.instructions).toBe("Keep answers short."); }); + // Real Claude Code 2.1.84 emits the billing header as a standalone block[0] + // with per-request rotating cc_version + cch. Tests must prove the strip is + // invariant across that rotation, otherwise the cache-buster leaks into + // `instructions` and tanks upstream prompt cache. + it.each([ + "x-anthropic-billing-header: cc_version=2.1.84.c8e; cc_entrypoint=cli; cch=da09b;", + "x-anthropic-billing-header: cc_version=2.1.84.76b; cc_entrypoint=cli; cch=46d1d;", + "x-anthropic-billing-header: cc_version=2.1.84.f51; cc_entrypoint=cli; cch=3c1ed;", + "x-anthropic-billing-header: cc_version=2.1.84.5b4; cc_entrypoint=cli; cch=8f29c;", + "x-anthropic-billing-header: cc_version=2.1.84.4f3; cc_entrypoint=cli; cch=d1658;", + ])("strips Claude Code billing header variant: %s", (billingText) => { + const result = translateAnthropicToCodexRequest( + makeRequest({ + system: [ + { type: "text" as const, text: billingText }, + { + type: "text" as const, + text: "You are Claude Code, Anthropic's official CLI for Claude.", + cache_control: { type: "ephemeral" }, + }, + { + type: "text" as const, + text: "\nYou are an interactive agent that helps users with software engineering tasks.", + cache_control: { type: "ephemeral" }, + }, + ], + }), + ); + expect(result.instructions).toBe( + "You are Claude Code, Anthropic's official CLI for Claude.\n\nYou are an interactive agent that helps users with software engineering tasks.", + ); + expect(result.instructions).not.toMatch(/cch=|cc_version=|x-anthropic-billing/); + }); + + it("produces identical instructions across rotating cc_version + cch values", () => { + const baseSystem = (billingText: string) => [ + { type: "text" as const, text: billingText }, + { + type: "text" as const, + text: "You are Claude Code, Anthropic's official CLI for Claude.", + cache_control: { type: "ephemeral" as const }, + }, + ]; + const a = translateAnthropicToCodexRequest( + makeRequest({ + system: baseSystem( + "x-anthropic-billing-header: cc_version=2.1.84.c8e; cc_entrypoint=cli; cch=da09b;", + ), + }), + ); + const b = translateAnthropicToCodexRequest( + makeRequest({ + system: baseSystem( + "x-anthropic-billing-header: cc_version=2.1.84.4f3; cc_entrypoint=cli; cch=d1658;", + ), + }), + ); + expect(a.instructions).toBe(b.instructions); + }); + it("falls back to default instructions when no system provided", () => { const result = translateAnthropicToCodexRequest(makeRequest()); expect(result.instructions).toBe("You are a helpful assistant.");