Skip to content

Missing {env:VAR} in config silently resolves to empty string #33853

Closed as not planned

Description

@eilon-onyx

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

  1. 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}" }).
  2. Run with SOME_TOKEN unset.
  3. 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.

Activity

  1. github-actions commented on Jun 25, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    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.

  2. x93008 commented on Jun 29, 2026

    @x93008

    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.

    The code path is present on current dev as well.

  3. olaf-wagner commented on Aug 20, 2026

    @olaf-wagner

    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:

    • opencode 1.18.18 on Linux
    • 5 remote MCP servers with "headers": { "Authorization": "Bearer {env:MY_TOKEN}" }
    • A plugin injects tokens into process.env at init time (before config load)
    • opencode debug config resolves {env:MY_TOKEN} correctly — shows "Authorization": "Bearer <actual_token>"
    • The env var is confirmed present in process.env

    What happens:

    • 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:

    1. The substitution runs at config load but the substituted headers aren't passed to the MCP transport's requestInit, or
    2. The MCP transport re-reads the raw (unsubstituted) config, or
    3. 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.

  4. github-actions commented on Oct 20, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions