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

Closed
opened 2026-02-16 18:11:41 -05:00 by yindo · 0 comments
Owner

Originally created by @kdcokenny on GitHub (Feb 11, 2026).

Originally assigned to: @thdxr on GitHub.

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

Originally created by @kdcokenny on GitHub (Feb 11, 2026). Originally assigned to: @thdxr on GitHub. ## 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: ```bash export MY_KEY="sk-test-12345" ``` 2. Launch opencode with inline config containing an `{env:}` token: ```bash 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 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. ```diff - 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 - #11628 — config precedence fix (already merged) - #5299, #1440, #9086, #10458 — other substitution-related issues ## OpenCode version 1.1.55
yindo closed this issue 2026-02-16 18:11:41 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#9122