Repository navigation
Description
Description
When a provider's options.apiKey uses {env:VAR} syntax and the env var is not set, the {env:} substitution replaces it with an empty string "" (not undefined). This empty string then blocks the auth.json fallback at provider.ts:1686, causing opencode to send an empty/missing API key to the provider and fail with "Failed to initialize provider. Check credentials and configuration."
Expected behavior
When {env:VAR} resolves to empty (env var unset), opencode should fall back to credentials stored in auth.json (via /connect or opencode auth login) — since auth.json is the documented credential store and {env:} is documented as returning empty string when unset.
Actual behavior
The auth.json fallback is silently skipped. The SDK receives apiKey: "", sends no Authorization header (or sends Bearer ), and the API returns 401. The user sees: Failed to initialize provider "<id>". Check credentials and configuration.
Root cause
Two subsystems disagree on the semantics of "missing key":
-
{env:}substitution (packages/opencode/src/config/variable.tsL36-37) returns""for unset env vars:text.replace(/\{env:([^}]+)\}/g, (_, varName) => { return (input.env?.[varName] ?? process.env[varName]) || "" })
Docs confirm: "If the environment variable is not set, it will be replaced with an empty string."
-
auth.json fallback (
packages/opencode/src/provider/provider.tsL1686) uses strict equality:if (options["apiKey"] === undefined && provider.key) options["apiKey"] = provider.key
"" === undefinedisfalse, so the fallback never fires.
Reproduction
opencode auth login→ Other →my-provider→ paste a valid API key (stored in~/.local/share/opencode/auth.json)- Create
opencode.jsonc:{ "$schema": "https://opencode.ai/config.json", "provider": { "my-provider": { "npm": "@ai-sdk/openai-compatible", "options": { "baseURL": "https://api.example.com/v1", "apiKey": "{env:MY_API_KEY}" }, "models": { "my-model": { "name": "My Model" } } } } } - Do NOT set
MY_API_KEYenv var. opencode run "hello"— observe the error:Failed to initialize provider "my-provider". Check credentials and configuration.- Expected: opencode falls back to the key in
auth.jsonand succeeds.
Fix
packages/opencode/src/provider/provider.ts L1686 — change strict equality to falsy check:
- if (options["apiKey"] === undefined && provider.key) options["apiKey"] = provider.key
+ if (!options["apiKey"] && provider.key) options["apiKey"] = provider.keyThis treats undefined, "", and null uniformly as "no key configured", allowing the auth.json fallback to take effect.
Environment
- opencode: v1.17.11
- OS: Linux
Activity
github-actions commentedon Jun 29, 2026
This issue might be a duplicate of or closely related to existing issues. Please check:
{env:...}substitution not working forapiKeyin custom provider options —{file:...}works #19946:{env:...}substitution not working forapiKeyin custom provider options — reports the same substitution mechanism for custom providerapiKey, though that issue focuses on the env var being set but not resolving, while this issue focuses on the unset-var-empty-string blocking theauth.jsonfallback. They may share a common root cause.${env:VAR}substitution in provider apiKey and baseURL override from models catalog #27853: Reports${env:VAR}substitution failing in providerapiKey(similar substitution issue, different syntax variant)If your issue is distinct from the above (as it appears to be — specifically the interaction between empty-string substitution and the
auth.jsonfallback), feel free to keep this open and note the distinction.x93008 commentedon Jun 29, 2026
Duplicate of #33853 — same root cause:
ConfigVariable.substitute()inpackages/opencode/src/config/variable.tssilently resolves unset{env:VAR}to empty string. #33853 covers the MCP auth scenario; this issue covers the provider apiKey scenario. Closing in favor of the earlier report.