You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Missing {env:VAR} in config silently resolves to empty string #33853
A {file:path} that doesn't exist throws a clear config error, but a {env:VAR} for an unset variable silently resolves to "". When that empty value is an MCP server's auth (an environment entry or a remote headers entry), the server starts with an empty header and the failure shows up much later as MCP error -32000: Connection closed, with nothing pointing back at the unset variable.
Steps to reproduce
In opencode.json, add an MCP server whose auth uses {env:SOME_TOKEN} (e.g. a remote server with "headers": { "Authorization": "Bearer {env:SOME_TOKEN}" }, or a local one with "environment": { "AUTH": "{env:SOME_TOKEN}" }).
Run with SOME_TOKEN unset.
The server fails to connect; for a stdio server it's an opaque -32000 with no mention of SOME_TOKEN.
Expected: a missing {env:VAR} is reported at config load (like a missing {file:}), naming the variable.
Where:ConfigVariable.substitute() in packages/opencode/src/config/variable.ts (current dev) — a missing env var falls through (input.env?.[name] ?? process.env[name]) || "" and becomes "".
OpenCode version: reproduced on 1.17.9; code path present on current dev. OS / Terminal: macOS.
If your issue is distinct (specifically about the missing-variable error being silent rather than substitution not working at all), please clarify the difference and consider linking those issues for context.
Confirmed on Linux and Windows, opencode v1.17.11 — same root cause (ConfigVariable.substitute() in packages/opencode/src/config/variable.ts silently resolves unset {env:VAR} to empty string).
In addition to the MCP auth scenario described above, this also affects provider options.apiKey when used with {env:VAR}: the empty string falls through to the SDK, which sends no Authorization header (or Bearer ), resulting in Failed to initialize provider "<id>". Check credentials and configuration. The auth.json fallback at provider.ts:1686 (if (options["apiKey"] === undefined && provider.key)) does not catch it because "" === undefined is false.
We're seeing this bug with MCP remote server headers, with an additional twist: the env var is set in process.env, yet {env:...} still resolves to empty at runtime.
All 5 MCP servers fail with 401 from the remote endpoint
Server-side logs show Authorization: Bearer (empty — no token value)
{file:/path/to/token} works perfectly as a workaround — same setup, just {file:...} instead of {env:...}
Key observation: opencode debug config (separate CLI process) resolves {env:...} correctly, but the running opencode session's MCP transport gets the empty value. This suggests either:
The substitution runs at config load but the substituted headers aren't passed to the MCP transport's requestInit, or
The MCP transport re-reads the raw (unsubstituted) config, or
process.env isn't available at the point where the MCP transport resolves headers
The {file:...} path works because it reads from disk at substitution time, while {env:...} depends on process.env being available in the right context.
Workaround: Use {file:/path/to/token} instead of {env:TOKEN} — this works at runtime for MCP remote server headers.
Activity
github-actions commentedon Jun 25, 2026
This issue might be a duplicate of existing issues. Please check:
{env:...}substitution not working forapiKeyin custom provider options —{file:...}works #19946:{env:...}substitution not working forapiKeyin custom provider options — reports{env:VAR}resolving silently to empty/nothing${env:VAR}substitution in provider apiKey and baseURL override from models catalog #27853:${env:VAR}substitution in provider apiKey and baseURL — also covers silent substitution failuresIf your issue is distinct (specifically about the missing-variable error being silent rather than substitution not working at all), please clarify the difference and consider linking those issues for context.
x93008 commentedon Jun 29, 2026
Confirmed on Linux and Windows, opencode v1.17.11 — same root cause (
ConfigVariable.substitute()inpackages/opencode/src/config/variable.tssilently resolves unset{env:VAR}to empty string).In addition to the MCP auth scenario described above, this also affects provider
options.apiKeywhen used with{env:VAR}: the empty string falls through to the SDK, which sends noAuthorizationheader (orBearer), resulting inFailed to initialize provider "<id>". Check credentials and configuration.The auth.json fallback atprovider.ts:1686(if (options["apiKey"] === undefined && provider.key)) does not catch it because"" === undefinedisfalse.The code path is present on current
devas well.olaf-wagner commentedon Aug 20, 2026
We're seeing this bug with MCP remote server headers, with an additional twist: the env var is set in
process.env, yet{env:...}still resolves to empty at runtime.Setup:
"headers": { "Authorization": "Bearer {env:MY_TOKEN}" }process.envat init time (before config load)opencode debug configresolves{env:MY_TOKEN}correctly — shows"Authorization": "Bearer <actual_token>"process.envWhat happens:
Authorization: Bearer(empty — no token value){file:/path/to/token}works perfectly as a workaround — same setup, just{file:...}instead of{env:...}Key observation:
opencode debug config(separate CLI process) resolves{env:...}correctly, but the running opencode session's MCP transport gets the empty value. This suggests either:requestInit, orprocess.envisn't available at the point where the MCP transport resolves headersThe
{file:...}path works because it reads from disk at substitution time, while{env:...}depends onprocess.envbeing available in the right context.Workaround: Use
{file:/path/to/token}instead of{env:TOKEN}— this works at runtime for MCP remote server headers.github-actions commentedon Oct 20, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.