Skip to content

OPENCODE_CONFIG_CONTENT bypasses {env:} and {file:} token substitution #13219

@kdcokenny

Description

@kdcokenny

Description

OPENCODE_CONFIG_CONTENT (inline config via environment variable) does not perform {env:VAR} or {file:path} token substitution. File-based config sources all go through loadFile() → load(), which applies regex-based substitution for both token types. The inline branch at line 179 of packages/opencode/src/config/config.ts calls JSON.parse() directly, skipping the load() pipeline entirely.

This means tokens like {env:MY_API_KEY} or {file:./secrets.txt} remain as literal strings in the parsed config when delivered via OPENCODE_CONFIG_CONTENT, but are correctly resolved when the same config is in a file. This is an inconsistency across config sources.

Steps to reproduce

  1. Set an environment variable:

    export MY_KEY="sk-test-12345"
  2. Launch opencode with inline config containing an {env:} token:

    OPENCODE_CONFIG_CONTENT='{"provider":{"my-provider":{"api_key":"{env:MY_KEY}"}}}' opencode
  3. Observe the provider's api_key is the literal string {env:MY_KEY}, not sk-test-12345.

  4. Place the identical JSON in opencode.json and launch normally — the token resolves correctly.

Expected behavior: {env:MY_KEY} resolves to sk-test-12345 regardless of whether config is file-based or inline.

Actual behavior: Inline config retains the literal token string.

Root cause

In packages/opencode/src/config/config.ts:

  • Line 179 — inline path uses raw JSON.parse(Flag.OPENCODE_CONFIG_CONTENT), bypassing substitution.
  • Line 1233loadFile() delegates to load(text, filepath) which performs {env:} (line 1238) and {file:} (line 1242) substitution before parsing.

Proposed fix direction

Route the inline config string through the existing load(text, configFilepath) function instead of raw JSON.parse. A synthetic configFilepath anchored to the project root (e.g. path.join(cwd, "<inline>")) would give {file:} tokens a sensible base directory for relative path resolution.

- result = mergeConfigConcatArrays(result, JSON.parse(Flag.OPENCODE_CONFIG_CONTENT))
+ result = mergeConfigConcatArrays(result, await load(Flag.OPENCODE_CONFIG_CONTENT, path.join(cwd, "<inline>")))

Non-goals

This issue is specifically about token substitution parity. It does not propose any change to config source precedence (that was addressed separately in #11628).

Related issues

OpenCode version

1.1.55

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