fix(core): subscription logins win over API keys for their CLI
Summary
A subscription login now wins over an API key for the CLI it belongs to. Requested by Khaliq for self-hosted Coding agents; it applies to any run.
A run can receive both kinds of credential for the same provider. For example, a Claude setup token (CLAUDE_CODE_OAUTH_TOKEN) for the coding agent and ANTHROPIC_API_KEY for programmatic LLM calls. The Claude CLI prefers the API key when both are set, so the run billed the API instead of using the selected subscription.
| CLI | Subscription detected by | Dropped from that CLI's agents |
|---|---|---|
| claude | CLAUDE_CODE_OAUTH_TOKEN (non-empty), or claude in AGENT_RELAY_SUBSCRIPTION_LOGINS | ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN |
| codex | codex in AGENT_RELAY_SUBSCRIPTION_LOGINS (a materialised $CODEX_HOME/auth.json ChatGPT login) | OPENAI_API_KEY, CODEX_API_KEY |
| grok | grok in AGENT_RELAY_SUBSCRIPTION_LOGINS ($GROK_HOME/auth.json) | XAI_API_KEY |
AGENT_RELAY_SUBSCRIPTION_LOGINS is set by agent-relay cloud worker when it mounts login files (AgentWorkforce/relay, feat/cloud-worker-cli-credentials).
Where the rule is applied (packages/core/src/credential-precedence.ts)
- Broker env (
HarnessDriverClient.spawn): every shadowed key is set toundefined. The broker spawns all PTY agents and inheritsprocess.env, so this explicitly removes the inherited value; leaving the key out would let it leak back. - Interactive agents (
spawnPty):- the agent's own env drops the keys shadowed for its CLI;
agentCredentialEnvgives the other keys back, so an opencode agent in the same run still getsANTHROPIC_API_KEYandOPENAI_API_KEY.
- Non-interactive agents and
process-spawnerrunCommand: the keys shadowed for that CLI are stripped. - Unchanged:
- The runner's own
apisteps readprocess.env, soctx.llmkeeps the API key. - Proxy-mode overrides are still applied last.
- The runner's own
Tests
credential-precedence.test.ts:- Claude, Codex and Grok rules;
- no subscription means no change;
- a whitespace-only token is ignored;
- the broker drops the keys, and each agent gets back only what its CLI may use.
completion-pipeline.test.ts: a run with both a Claude setup token and an Anthropic key.- The broker env has
ANTHROPIC_API_KEY/OPENAI_API_KEYexplicitly undefined. - The Claude agent's spawn env has no
ANTHROPIC_API_KEYbut keepsCLAUDE_CODE_OAUTH_TOKEN. - The opencode agent keeps both keys.
- The broker env has
- On sf-frame:
npm ci,npm run typecheck,npm test(65 files, 1129 tests) pass.
馃 Generated with Claude Code
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Open in [code]smith 路 Need help on this PR? Tag @codesmith-bot with what you need.
Agent Relay sessions
claudesessiona19e66d9路 opened viagh pr create路 last active 2026-11-04