From 3a23f3a0a36186079d8f8cfecf3c6a2af9f29cf0 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 14 Mar 2026 22:00:42 +0000 Subject: [PATCH 1/4] fix: resolve {env:VAR} placeholders in config at runtime MCP secrets are often scrubbed and replaced with {env:VAR} placeholders by the opencode-synced plugin. However, these placeholders were not being resolved by opencode at runtime, causing authentication failures. This change implements a recursive environment variable resolution mechanism in `applyOverridesToRuntimeConfig` to ensure that these placeholders are correctly replaced with values from `process.env`. Added tests to verify resolution of nested placeholders and handling of missing environment variables. Co-authored-by: iHildy <25069719+iHildy@users.noreply.github.com> --- src/sync/config.ts | 26 ++++++++++++++++- src/sync/resolve.test.ts | 61 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 src/sync/resolve.test.ts diff --git a/src/sync/config.ts b/src/sync/config.ts index b0d4af5..7ba2952 100644 --- a/src/sync/config.ts +++ b/src/sync/config.ts @@ -185,10 +185,34 @@ export function applyOverridesToRuntimeConfig( overrides: Record ): void { const merged = deepMerge(config, overrides) as Record; + const resolved = resolveEnvPlaceholders(merged); for (const key of Object.keys(config)) { delete config[key]; } - Object.assign(config, merged); + Object.assign(config, resolved); +} + +export function resolveEnvPlaceholders(config: unknown): unknown { + if (typeof config === 'string') { + return config.replace(/\{env:([^}]+)\}/g, (match, envVar) => { + const value = process.env[envVar]; + return value !== undefined ? value : match; + }); + } + + if (Array.isArray(config)) { + return config.map((item) => resolveEnvPlaceholders(item)); + } + + if (isPlainObject(config)) { + const result: Record = {}; + for (const [key, value] of Object.entries(config)) { + result[key] = resolveEnvPlaceholders(value); + } + return result; + } + + return config; } export function deepMerge(base: T, override: unknown): T { diff --git a/src/sync/resolve.test.ts b/src/sync/resolve.test.ts new file mode 100644 index 0000000..462a15e --- /dev/null +++ b/src/sync/resolve.test.ts @@ -0,0 +1,61 @@ +import { describe, expect, it } from 'vitest'; +import { applyOverridesToRuntimeConfig } from './config.js'; + +describe('applyOverridesToRuntimeConfig environment resolution', () => { + it('resolves {env:VAR} placeholders from process.env', () => { + process.env.TEST_VAR = 'secret-value'; + process.env.OTHER_VAR = 'other-value'; + + const config: Record = { + mcp: { + github: { + headers: { + Authorization: 'Bearer {env:TEST_VAR}', + }, + other: '{env:OTHER_VAR}', + }, + }, + unrelated: 'keep-me', + }; + + const overrides = { + mcp: { + github: { + headers: { + Authorization: 'Bearer {env:TEST_VAR}', + }, + }, + }, + }; + + // We apply overrides (which might already contain placeholders) + applyOverridesToRuntimeConfig(config, overrides); + + const mcp = config.mcp as Record>>; + expect(mcp.github.headers.Authorization).toBe('Bearer secret-value'); + expect(mcp.github.other).toBe('other-value'); + expect(config.unrelated).toBe('keep-me'); + + delete process.env.TEST_VAR; + delete process.env.OTHER_VAR; + }); + + it('handles missing environment variables by leaving placeholder intact', () => { + process.env.PRESENT_VAR = 'present'; + delete process.env.ABSENT_VAR; + + const config: Record = { + val: '{env:PRESENT_VAR}', + missing: '{env:ABSENT_VAR}', + }; + + applyOverridesToRuntimeConfig(config, {}); + + expect(config.val).toBe('present'); + // If it's missing, we keep it as is to avoid breaking things silently or passing empty strings + // that might be harder to debug. + expect(config.missing).toBe('{env:ABSENT_VAR}'); + + delete process.env.PRESENT_VAR; + }); +}); From 8b2e53d61ec5916e2fbd01c175098d05269ef1dd Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 14 Mar 2026 22:01:31 +0000 Subject: [PATCH 2/4] fix: resolve {env:VAR} placeholders in config at runtime MCP secrets are often scrubbed and replaced with {env:VAR} placeholders by the opencode-synced plugin. However, these placeholders were not being resolved by opencode at runtime, causing authentication failures. This change implements a recursive environment variable resolution mechanism in `applyOverridesToRuntimeConfig` to ensure that these placeholders are correctly replaced with values from `process.env`. Added tests to verify resolution of nested placeholders and handling of missing environment variables. Co-authored-by: iHildy <25069719+iHildy@users.noreply.github.com> From 0599e0a87c8b6a7808c30f49e19cfa396f1d0fcf Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 14 Mar 2026 22:03:13 +0000 Subject: [PATCH 3/4] fix: resolve {env:VAR} placeholders in config at runtime MCP secrets are often scrubbed and replaced with {env:VAR} placeholders by the opencode-synced plugin. However, these placeholders were not being resolved by opencode at runtime, causing authentication failures. This change implements a recursive environment variable resolution mechanism in `applyOverridesToRuntimeConfig` to ensure that these placeholders are correctly replaced with values from `process.env`. Added tests to verify resolution of nested placeholders and handling of missing environment variables. Co-authored-by: iHildy <25069719+iHildy@users.noreply.github.com> From 96f398834aca0c8811f3bd6194c8f0a67bcf3a2a Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 14 Mar 2026 22:04:43 +0000 Subject: [PATCH 4/4] fix: resolve {env:VAR} placeholders in config at runtime MCP secrets are often scrubbed and replaced with {env:VAR} placeholders by the opencode-synced plugin. However, these placeholders were not being resolved by opencode at runtime, causing authentication failures. This change implements a recursive environment variable resolution mechanism in `applyOverridesToRuntimeConfig` to ensure that these placeholders are correctly replaced with values from `process.env`. Added tests to verify resolution of nested placeholders and handling of missing environment variables. Co-authored-by: iHildy <25069719+iHildy@users.noreply.github.com>