revert(core): undo x-api-key + Authorization double-emit (#4342) — regresses IdeaLab-style proxies#4385
Conversation
… outbound (#4323) (#4342)" This reverts commit 4b25f9c. PR #4342 unconditionally injected `x-api-key` alongside `Authorization: Bearer` on every proxy-branch request. This broke IdeaLab-style proxies (e.g. `idealab.alibaba-inc.com/api/anthropic`) which reject requests carrying both headers with HTTP 401: 鉴权header, x-api-key和Authorization不可以同时存在 (auth header: x-api-key and Authorization cannot coexist) The two proxy families have mutually exclusive header contracts — OpenCode-Go-style servers want `x-api-key` only, IdeaLab-style servers want `Authorization` only — so a one-size-fits-all default cannot satisfy both at once. Reverting restores the pre-#4342 default (Bearer only) so IdeaLab users are unblocked. OpenCode-Go-style users can opt in via `customHeaders`: { "customHeaders": { "x-api-key": "<key>" } } `buildHeaders` already merges customHeaders into defaultHeaders (only `anthropic-beta` is reserved for per-request handling), and on the proxy branch the SDK is constructed with `apiKey: null` so it does not emit its own `x-api-key` — the value on the wire comes solely from the user's explicit customHeaders entry, preserving the #4020 env-leak guard. Reopens #4323.
📋 Review SummaryThis PR cleanly reverts #4342, which unconditionally added 🔍 General FeedbackPositive aspects:
Key insight from PR description: { "customHeaders": { "x-api-key": "<their-key>" } }This is the correct escape hatch pattern — users with non-standard auth requirements opt in explicitly. 🎯 Specific Feedback🔴 CriticalNo critical issues identified — this is a straightforward revert that fixes a production-breaking regression for IdeaLab users. 🟡 HighNo high-priority issues. The revert correctly addresses the mutual exclusivity problem between IdeaLab proxies (Bearer-only) and OpenCode-Go proxies (x-api-key-only). 🟢 MediumFile: The reverted test description mentions "x-api-key" in the test name but the post-revert behavior no longer includes this header. Consider if any test name cleanup is needed for future tests that might be added to cover the Suggestion: Consider adding a regression test that explicitly documents the it('customHeaders can provide x-api-key for OpenCode-Go-style proxies', async () => {
// Users whose backend requires x-api-key (not Bearer) can use customHeaders
// as the escape hatch — see #4323, #4385
const { AnthropicContentGenerator } = await importGenerator();
void new AnthropicContentGenerator(
{
model: 'claude-test',
apiKey: 'bearer-token',
baseUrl: 'https://opencode.example.com',
timeout: 10_000,
maxRetries: 2,
samplingParams: {},
schemaCompliance: 'auto',
customHeaders: {
'x-api-key': 'opencode-key',
},
},
mockConfig,
);
const headers = (anthropicState.constructorOptions?.['defaultHeaders'] ||
{}) as Record<string, string>;
expect(headers['x-api-key']).toBe('opencode-key');
expect(anthropicState.constructorOptions?.['authToken']).toBe('bearer-token');
});🔵 LowFile: The file contains extensive inline documentation about the auth header decision-making process. After the revert, ensure any lingering comments about "dual-header" or "both auth shapes" are cleaned up if they no longer apply. The diff shows the removed code block had 16 lines of comments explaining the dual-header rationale — verify no orphaned references remain in surrounding code comments. Documentation follow-up:
✅ Highlights
Recommendation: ✅ LGTM — Approve and merge This revert is the correct response to a production-breaking change. The trade-off (OpenCode-Go users need a one-line config change vs. IdeaLab users being completely blocked) is fair and well-justified. |
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
… outbound (#4323) (#4342)" (#4385) This reverts commit 4b25f9c. PR #4342 unconditionally injected `x-api-key` alongside `Authorization: Bearer` on every proxy-branch request. This broke IdeaLab-style proxies (e.g. `idealab.alibaba-inc.com/api/anthropic`) which reject requests carrying both headers with HTTP 401: 鉴权header, x-api-key和Authorization不可以同时存在 (auth header: x-api-key and Authorization cannot coexist) The two proxy families have mutually exclusive header contracts — OpenCode-Go-style servers want `x-api-key` only, IdeaLab-style servers want `Authorization` only — so a one-size-fits-all default cannot satisfy both at once. Reverting restores the pre-#4342 default (Bearer only) so IdeaLab users are unblocked. OpenCode-Go-style users can opt in via `customHeaders`: { "customHeaders": { "x-api-key": "<key>" } } `buildHeaders` already merges customHeaders into defaultHeaders (only `anthropic-beta` is reserved for per-request handling), and on the proxy branch the SDK is constructed with `apiKey: null` so it does not emit its own `x-api-key` — the value on the wire comes solely from the user's explicit customHeaders entry, preserving the #4020 env-leak guard. Reopens #4323.
Summary
Reverts #4342. That PR unconditionally injected
x-api-keyalongside the existingAuthorization: Bearerheader on every proxy-branch Anthropic request to satisfy #4323-style servers (OpenCode-Go, Claude proxy products) that authenticate only onx-api-key. The side effect: IdeaLab-style Anthropic-compatible proxies (e.g.https://idealab.alibaba-inc.com/api/anthropic) explicitly reject requests carrying both auth headers and return HTTP 401:After #4342 landed, every IdeaLab user is hard-blocked from sending a single message. The two proxy families have mutually exclusive header contracts — there is no default that satisfies both at once — so the right place for the choice is user configuration, not a hard-coded default.
Why customHeaders is the right escape hatch (and PR #4342 wasn't needed)
buildHeadersalready mergescustomHeadersinto the SDK'sdefaultHeaders(it only reservesanthropic-betafor per-request handling — every other key passes through verbatim). On the proxy branch the SDK is constructed withapiKey: nullso it does not emit its ownx-api-key. That means a user whose backend wantsx-api-keycan already do this in their settings without any core change:{ "customHeaders": { "x-api-key": "<their-key>" } }The wire value comes solely from the user's explicit entry — env back-fill stays suppressed, so the #4020 leak guard is preserved.
What changes / who is affected
api.anthropic.comx-api-key)Net effect: IdeaLab users are unblocked today; OpenCode-Go users need a one-line settings change instead of an automatic default. That's a fair trade — the non-mainstream-proxy users opt in, the broader user base isn't broken by it.
Reopens
authStyleconfig option; if future user demand justifies a first-class switch, it can be added later without touching this revert.Test plan
vitest run src/core/anthropicContentGenerator/anthropicContentGenerator.test.ts— 65 tests pass (back to the pre-fix(core): set x-api-key alongside Authorization on Anthropic outbound (#4323) #4342 count; the 89 lines of tests fix(core): set x-api-key alongside Authorization on Anthropic outbound (#4323) #4342 added are reverted together with the code change).git revertwas a clean auto-merge against current main — no unrelated downstream change touched the constructor or test file between fix(core): set x-api-key alongside Authorization on Anthropic outbound (#4323) #4342 and HEAD./modelworks (auth doesn't gate model listing) but the firstmessages.createreturns HTTP 401 with the message quoted above. Reverting the constructor injection removes the offending header and restores the pre-fix(core): set x-api-key alongside Authorization on Anthropic outbound (#4323) #4342 Bearer-only path that worked before.