From 2f3291d2805894589e0f00f9c4d9aa5c4f3c7da2 Mon Sep 17 00:00:00 2001 From: MoerAI Date: Mon, 27 Apr 2026 20:23:15 +0900 Subject: [PATCH] fix(bun-install): forward process.env so child bun install inherits proxy settings (fixes #3528) Root cause: runBunInstallWithDetails() invoked spawnWithWindowsHide() without passing env, so the child bun install lost https_proxy / http_proxy / NO_PROXY and other parent env vars. @npmcli/agent then received an empty proxy URL and rejected fetch with 'fetch() proxy.url must be a non-empty string', breaking plugin auto-install on networks that require an outbound proxy. Fix: pass env: process.env to spawnWithWindowsHide so the child bun install inherits the full parent environment, including proxy variables. Verification: added a regression test that sets https_proxy/http_proxy on process.env and asserts those values are forwarded to the spawn options. Test fails before the fix and passes after. typecheck clean. Other pre-existing path-separator test failures on Windows are unrelated to this change. --- src/cli/config-manager/bun-install.test.ts | 30 ++++++++++++++++++++++ src/cli/config-manager/bun-install.ts | 1 + 2 files changed, 31 insertions(+) diff --git a/src/cli/config-manager/bun-install.test.ts b/src/cli/config-manager/bun-install.test.ts index 5564b3ff6..d516a59e2 100644 --- a/src/cli/config-manager/bun-install.test.ts +++ b/src/cli/config-manager/bun-install.test.ts @@ -70,12 +70,40 @@ describe("runBunInstallWithDetails", () => { expect(getOpenCodeCacheDirSpy).toHaveBeenCalledTimes(1) expect(spawnWithWindowsHideSpy).toHaveBeenCalledWith(["bun", "install"], { cwd: "/tmp/opencode-cache/packages", + env: process.env, stdout: "pipe", stderr: "pipe", }) }) }) + describe("#when bun install runs with proxy environment variables set", () => { + it("#then forwards process.env so child bun install inherits proxy settings (issue #3528)", async () => { + // given + const originalHttpsProxy = process.env.https_proxy + const originalHttpProxy = process.env.http_proxy + process.env.https_proxy = "http://proxy.example.com:3128" + process.env.http_proxy = "http://proxy.example.com:3128" + + try { + // when + await runBunInstallWithDetails() + + // then + const callArgs = spawnWithWindowsHideSpy.mock.calls[0] + const spawnOptions = callArgs?.[1] as { env?: Record } | undefined + expect(spawnOptions?.env).toBeDefined() + expect(spawnOptions?.env?.https_proxy).toBe("http://proxy.example.com:3128") + expect(spawnOptions?.env?.http_proxy).toBe("http://proxy.example.com:3128") + } finally { + if (originalHttpsProxy === undefined) delete process.env.https_proxy + else process.env.https_proxy = originalHttpsProxy + if (originalHttpProxy === undefined) delete process.env.http_proxy + else process.env.http_proxy = originalHttpProxy + } + }) + }) + describe("#when bun install uses piped output", () => { it("#then passes pipe mode to the spawned process", async () => { // given @@ -87,6 +115,7 @@ describe("runBunInstallWithDetails", () => { expect(result).toEqual({ success: true }) expect(spawnWithWindowsHideSpy).toHaveBeenCalledWith(["bun", "install"], { cwd: "/tmp/opencode-cache/packages", + env: process.env, stdout: "pipe", stderr: "pipe", }) @@ -104,6 +133,7 @@ describe("runBunInstallWithDetails", () => { expect(result).toEqual({ success: true }) expect(spawnWithWindowsHideSpy).toHaveBeenCalledWith(["bun", "install"], { cwd: "/tmp/opencode-cache/packages", + env: process.env, stdout: "inherit", stderr: "inherit", }) diff --git a/src/cli/config-manager/bun-install.ts b/src/cli/config-manager/bun-install.ts index 82f49c2e6..b744902f1 100644 --- a/src/cli/config-manager/bun-install.ts +++ b/src/cli/config-manager/bun-install.ts @@ -85,6 +85,7 @@ export async function runBunInstallWithDetails(options?: RunBunInstallOptions): try { const proc = spawnWithWindowsHide(["bun", "install"], { cwd: cacheDir, + env: process.env, stdout: outputMode, stderr: outputMode, })