fix(acp): warn on Claude OAuth token + ANTHROPIC_API_KEY conflict too (#1279)

ACP_CREDENTIAL_CONFLICTS only listed [CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_BASE_URL].
The SDK strips BOTH ANTHROPIC_API_KEY and ANTHROPIC_BASE_URL when the OAuth token
is active (software-agent-sdk#3588), so a co-present API key is silently ignored
at runtime too. Add the [CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_API_KEY] pair so the
onboarding + settings credential forms warn about it, matching the SDK behavior.

Co-authored-by: Debug Agent <simon@openhands.dev>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
simonrosenberg
2026-06-09 15:25:02 +02:00
committed by GitHub
co-authored by Debug Agent Claude Opus 4.8
parent 9f201e8c02
commit 254c7c9570
2 changed files with 40 additions and 5 deletions
+27
View File
@@ -208,6 +208,33 @@ describe("getAcpCredentialConflicts", () => {
).toEqual([["CLAUDE_CODE_OAUTH_TOKEN", "ANTHROPIC_BASE_URL"]]);
});
it("flags the Claude OAuth token + API key pair when both are set", () => {
// The SDK strips ANTHROPIC_API_KEY when the OAuth token is active
// (software-agent-sdk#3588), so the key would be silently ignored.
expect(
getAcpCredentialConflicts(
"claude-code",
has("CLAUDE_CODE_OAUTH_TOKEN", "ANTHROPIC_API_KEY"),
),
).toEqual([["CLAUDE_CODE_OAUTH_TOKEN", "ANTHROPIC_API_KEY"]]);
});
it("flags both pairs when the token, API key, and base URL are all set", () => {
expect(
getAcpCredentialConflicts(
"claude-code",
has(
"CLAUDE_CODE_OAUTH_TOKEN",
"ANTHROPIC_API_KEY",
"ANTHROPIC_BASE_URL",
),
),
).toEqual([
["CLAUDE_CODE_OAUTH_TOKEN", "ANTHROPIC_API_KEY"],
["CLAUDE_CODE_OAUTH_TOKEN", "ANTHROPIC_BASE_URL"],
]);
});
it("stays quiet when only one side is set", () => {
expect(
getAcpCredentialConflicts("claude-code", has("CLAUDE_CODE_OAUTH_TOKEN")),
+13 -5
View File
@@ -269,13 +269,21 @@ const ACP_RESERVED_CREDENTIALS: Record<string, ACPProviderSecretField[]> = {
};
/**
* Credential pairs that break each other at runtime, keyed by provider.
* Claude's OAuth token authenticates against Anthropic directly; an
* ``ANTHROPIC_BASE_URL`` set alongside it silently routes requests elsewhere
* and breaks the token's bearer auth (see docs/ACP_AGENTS.md).
* Credential pairs that break each other at runtime, keyed by provider —
* mirrors the SDK's ``_ENV_CONFLICT_MAP`` (software-agent-sdk#3588). Claude's
* OAuth token (``CLAUDE_CODE_OAUTH_TOKEN``) authenticates against Anthropic
* directly, so when it is set the SDK strips:
* - ``ANTHROPIC_API_KEY`` — otherwise it takes precedence and silently
* bypasses the subscription;
* - ``ANTHROPIC_BASE_URL`` — otherwise it proxies the bearer to an endpoint
* that rejects it (see docs/ACP_AGENTS.md).
* Either one set alongside the token is silently ignored at runtime, so warn.
*/
const ACP_CREDENTIAL_CONFLICTS: Record<string, Array<[string, string]>> = {
"claude-code": [["CLAUDE_CODE_OAUTH_TOKEN", "ANTHROPIC_BASE_URL"]],
"claude-code": [
["CLAUDE_CODE_OAUTH_TOKEN", "ANTHROPIC_API_KEY"],
["CLAUDE_CODE_OAUTH_TOKEN", "ANTHROPIC_BASE_URL"],
],
};
/**