Repository navigation
Description
Summary
With the v2 shared background service, changing which well-known endpoints are authenticated (opencode auth login / opencode auth logout) does not update the provider's API-key substitution wiring. Requests keep resolving {env:VAR} for a previously-authenticated endpoint whose credential no longer exists, so an empty API key is sent and the upstream provider rejects the request with 401. v1.19.0 and v2 --standalone are unaffected.
Environment
- opencode version: 2.0.26 (also tested: 1.19.0)
- OS: Darwin 25.6.0 (macOS, arm64)
- Terminal: xterm-256color, TERM_PROGRAM=vscode, truecolor (observed from the reporting shell)
- Shell: /bin/zsh
- Install/channel: latest (local=false)
- Active plugins: oh-my-opencode-slim@3.0.3
Reproduction
Setup: two well-known endpoints, A and B. Each serves a manifest declaring a distinct auth.env name (VAR_A / VAR_B) and a remote config that sets the same provider's auth header to a different env name:
- A's remote config:
provider.<P>.options.headers["x-api-key"] = "{env:VAR_A}" - B's remote config: same header =
"{env:VAR_B}"
Each endpoint's auth.command returns a different API key, and each key is valid only for that endpoint's backend.
Steps (shared background service — normal opencode usage, not --standalone):
opencode auth login https://endpoint-a/...opencode run -m <P>/<model> "..."-> worksopencode auth login https://endpoint-b/...(both A and B authenticated)opencode run ...-> worksopencode auth logout https://endpoint-b/...(A authenticated again)opencode run ...-> fails: provider 401Invalid API Key— even though A is authenticated and A's credential is presentopencode auth logout https://endpoint-a/...thenopencode auth login https://endpoint-a/...-> works again (the credential matches the stale wiring)opencode service restartwhile A is authenticated -> still fails (the stale state survives a restart)
Control experiments (same sequences):
- v1.19.0: all steps pass.
- v2.0.26 with
--standalone: all steps pass. - v2.0.26 logged into both endpoints simultaneously (never logging out): works reliably.
Expected Behavior
After step 5, requests should use endpoint A's credential (A is the authenticated endpoint) and succeed. The provider's env-name wiring should follow the set of currently authenticated well-known endpoints.
Actual Behavior
Step 6 sends an empty/invalid API key and the provider returns 401 (Invalid API Key, provider error code 03 in this deployment's gateway). The auth state change itself reports success; the problem only appears when a request is made.
Additional Context
Server log excerpt (names genericized):
credential removed integrationID=https://endpoint-b/...
event.type=config.updated
event.type=provider.updated
...
Failed to drain Session cause="AI.Error: ... Error: Invalid API Key. Error Code: 03"
Notes:
wellknown:sourcespersists the list of endpoints ever seen, and cached well-known configs remain after logout. This appears to be why aservice restartdoes not clear the stale wiring — a fresh boot can re-apply the stale env-name mapping from the cached configs.- The provider's
apiKeyis set to a placeholder ("dummy") with the real key supplied only by the header substitution, so an unresolved{env:...}yields an invalid key rather than a clear missing-auth error. That may be why the failure surfaces as a provider 401 rather than an auth error. - Frequency: deterministic once both endpoints have been authenticated on the same service; in day-to-day use it looks intermittent because it only breaks while the wiring's env name has no matching credential (i.e. after logging out of that endpoint).
- Workarounds: stay logged into both endpoints; use
--standalone; or use v1.x. - Internal URLs, endpoint names, and credentials have been genericized.
Activity
opencode-agent commentedon Oct 11, 2026
Thanks for the detailed report. I reproduced this on 2.0.26, and it still happens on the latest
dev(707de58).I used a local mock with two well-known origins, A (
VAR_A) and B (VAR_B). Each remote config sets the same provider'sx-api-keyheader to{env:VAR_X}, withapiKey: "dummy". A mock gateway logs the key it receives:auth login A, thenrun→ sends A's key, worksauth login B, thenrun→ sends B's key, worksauth logout B, thenrun→ sendsx-api-key: ""and getsInvalid API Key. Error Code: 03, even thoughauth listshows A's credential is still thererunstill sends an empty keyThe script below reproduces it end to end.
From reading the code (not tested yet), the likely cause is this: logout removes only the credential, not the well-known source or its cached remote config. That cached config is still applied with no credential, so
{env:VAR_B}resolves to an empty string and overrides A's header. That would also explain why a restart doesn't clear it.Reproduction script (repro.sh)
Run it:
bash repro.shnosmile99 commentedon Oct 12, 2026
I reproduced this and intend to work on it — I have a fix with a regression test ready and will open a PR against
devshortly.Root cause (verified empirically): in
packages/core/src/config.ts(loadWellknownEntry), when the credential for a well-known endpoint is gone (logged out), the code falls through towellknown.cached(entry.origin)and substitutes the cached remote config withenv: undefined.ConfigVariable.substitutethen resolves{env:VAR_B}to"", so the logged-out endpoint still emits a provider document whose emptyx-api-keyheader overlays the still-authenticated endpoint's wiring (later document wins inmergeHeaders) — hence the 401. The stale state survives restarts because the cached config lives in KV until the source is removed.The fix: when the manifest declares
authbut no usable key credential exists, the entry contributes no documents, so the provider wiring follows the set of currently authenticated endpoints. A failed refresh with a credential present still keeps the last resolved config.nosmile99 commentedon Oct 12, 2026
PR is up: #54687 (against
dev, per the contribution gates). Root cause and verification are in the PR body; happy to adjust if the approach needs rework.