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
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions