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
-
Set an environment variable:
export MY_KEY="sk-test-12345"
-
Launch opencode with inline config containing an {env:} token:
OPENCODE_CONFIG_CONTENT='{"provider":{"my-provider":{"api_key":"{env:MY_KEY}"}}}' opencode
-
Observe the provider's api_key is the literal string {env:MY_KEY}, not sk-test-12345.
-
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 1233 —
loadFile() 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
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 throughloadFile() → load(), which applies regex-based substitution for both token types. The inline branch at line 179 ofpackages/opencode/src/config/config.tscallsJSON.parse()directly, skipping theload()pipeline entirely.This means tokens like
{env:MY_API_KEY}or{file:./secrets.txt}remain as literal strings in the parsed config when delivered viaOPENCODE_CONFIG_CONTENT, but are correctly resolved when the same config is in a file. This is an inconsistency across config sources.Steps to reproduce
Set an environment variable:
Launch opencode with inline config containing an
{env:}token:OPENCODE_CONFIG_CONTENT='{"provider":{"my-provider":{"api_key":"{env:MY_KEY}"}}}' opencodeObserve the provider's
api_keyis the literal string{env:MY_KEY}, notsk-test-12345.Place the identical JSON in
opencode.jsonand launch normally — the token resolves correctly.Expected behavior:
{env:MY_KEY}resolves tosk-test-12345regardless 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:JSON.parse(Flag.OPENCODE_CONFIG_CONTENT), bypassing substitution.loadFile()delegates toload(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 rawJSON.parse. A syntheticconfigFilepathanchored to the project root (e.g.path.join(cwd, "<inline>")) would give{file:}tokens a sensible base directory for relative path resolution.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
{env:...}variable substitution inconsistently fails for specific MCP server URLs #5299, file substitution in configuration results in "invalid JSON" #1440, Config variables inopencode.jsonoverwritten with actual values on start up #9086, [FEATURE]: Auto-load .env files in scoped directories for {env:VAR} substitution #10458 — other substitution-related issuesOpenCode version
1.1.55