Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
727f07dcd6 fix(child_process): don't inherit BUN_INSPECT* env vars
When VSCode's JavaScript Debug Terminal runs Bun, it sets BUN_INSPECT*
environment variables to enable debugging. If a process like Next.js
calls `child_process.fork()` or `child_process.spawn()`, these env vars
were inherited by child processes, causing them to attempt their own
debugger connections, which resulted in hangs.

This change filters out BUN_INSPECT, BUN_INSPECT_CONNECT_TO, and
BUN_INSPECT_NOTIFY environment variables in `normalizeSpawnArguments()`
to prevent this issue. This matches the existing behavior in `bunx`.

On Windows, env keys are case-insensitive, so the comparison is
normalized to uppercase.

Also fixes spawnSync to use the filtered environment (kBunEnv) instead
of the raw options.env.

Fixes #26704

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 09:30:16 +00:00
2 changed files with 114 additions and 1 deletions

View File

@@ -534,7 +534,7 @@ function spawnSync(file, args, options) {
// Bun.spawn() expects cmd[0] to be the command to run, and argv0 to replace the first arg when running the command,
// so we have to set argv0 to spawnargs[0] and cmd[0] to file
cmd: [options.file, ...Array.prototype.slice.$call(options.args, 1)],
env: options.env || undefined,
env: options[kBunEnv] || undefined,
cwd: options.cwd || undefined,
stdio: bunStdio,
windowsVerbatimArguments: options.windowsVerbatimArguments,
@@ -989,6 +989,18 @@ function normalizeSpawnArguments(file, args, options) {
for (const key of envKeys) {
const value = env[key];
if (value !== undefined) {
// Don't inherit debugger environment variables to child processes.
// This prevents hangs when forking from VSCode's JavaScript Debug Terminal.
// See: https://github.com/oven-sh/bun/issues/26704
// On Windows, env keys are case-insensitive, so normalize before comparing.
const keyNormalized = process.platform === "win32" ? StringPrototypeToUpperCase.$call(key) : key;
if (
keyNormalized === "BUN_INSPECT" ||
keyNormalized === "BUN_INSPECT_CONNECT_TO" ||
keyNormalized === "BUN_INSPECT_NOTIFY"
) {
continue;
}
validateArgumentNullCheck(key, `options.env['${key}']`);
validateArgumentNullCheck(value, `options.env['${key}']`);
bunEnv[key] = value;

View File

@@ -0,0 +1,101 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";
// https://github.com/oven-sh/bun/issues/26704
// Child processes should not inherit BUN_INSPECT* environment variables
// to prevent hangs when forking from VSCode's JavaScript Debug Terminal.
test("child_process.spawn does not inherit BUN_INSPECT* env vars", async () => {
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
const { spawn } = require("child_process");
const child = spawn(process.execPath, ["-e", "console.log(JSON.stringify({BUN_INSPECT: process.env.BUN_INSPECT, BUN_INSPECT_CONNECT_TO: process.env.BUN_INSPECT_CONNECT_TO, BUN_INSPECT_NOTIFY: process.env.BUN_INSPECT_NOTIFY}))"], { stdio: "inherit" });
child.on("exit", (code) => process.exit(code));
`,
],
env: {
...bunEnv,
BUN_INSPECT: "ws://127.0.0.1:6499",
BUN_INSPECT_CONNECT_TO: "ws://127.0.0.1:6500",
BUN_INSPECT_NOTIFY: "ws://127.0.0.1:6501",
},
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
const result = JSON.parse(stdout.trim());
expect(result.BUN_INSPECT).toBeUndefined();
expect(result.BUN_INSPECT_CONNECT_TO).toBeUndefined();
expect(result.BUN_INSPECT_NOTIFY).toBeUndefined();
expect(exitCode).toBe(0);
});
test("child_process.spawnSync does not inherit BUN_INSPECT* env vars", async () => {
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
const { spawnSync } = require("child_process");
const result = spawnSync(process.execPath, ["-e", "console.log(JSON.stringify({BUN_INSPECT: process.env.BUN_INSPECT, BUN_INSPECT_CONNECT_TO: process.env.BUN_INSPECT_CONNECT_TO, BUN_INSPECT_NOTIFY: process.env.BUN_INSPECT_NOTIFY}))"]);
console.log(result.stdout.toString());
process.exit(result.status);
`,
],
env: {
...bunEnv,
BUN_INSPECT: "ws://127.0.0.1:6499",
BUN_INSPECT_CONNECT_TO: "ws://127.0.0.1:6500",
BUN_INSPECT_NOTIFY: "ws://127.0.0.1:6501",
},
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
const result = JSON.parse(stdout.trim());
expect(result.BUN_INSPECT).toBeUndefined();
expect(result.BUN_INSPECT_CONNECT_TO).toBeUndefined();
expect(result.BUN_INSPECT_NOTIFY).toBeUndefined();
expect(exitCode).toBe(0);
});
test("child_process filters BUN_INSPECT* even when explicitly passed in options.env", async () => {
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
const { spawn } = require("child_process");
const child = spawn(process.execPath, ["-e", "console.log(JSON.stringify({BUN_INSPECT: process.env.BUN_INSPECT}))"], {
stdio: "inherit",
env: { ...process.env, BUN_INSPECT: "explicit-value" }
});
child.on("exit", (code) => process.exit(code));
`,
],
env: {
...bunEnv,
BUN_INSPECT: "ws://127.0.0.1:6499",
},
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
const result = JSON.parse(stdout.trim());
// When user explicitly passes env, the BUN_INSPECT should still be filtered
// since allowing it would cause the same hang issue
expect(result.BUN_INSPECT).toBeUndefined();
expect(exitCode).toBe(0);
});