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.
This commit is contained in:
MoerAI
2026-04-27 20:23:15 +09:00
parent 8ad50b7e93
commit 2f3291d280
2 changed files with 31 additions and 0 deletions
@@ -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<string, string | undefined> } | 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",
})
+1
View File
@@ -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,
})