diff --git a/src/bun.js.zig b/src/bun.js.zig index e17e20471f..337045915e 100644 --- a/src/bun.js.zig +++ b/src/bun.js.zig @@ -135,7 +135,7 @@ pub const Run = struct { null, ); try bundle.runEnvLoader(false); - const mini = jsc.MiniEventLoop.initGlobal(bundle.env); + const mini = jsc.MiniEventLoop.initGlobal(bundle.env, null); mini.top_level_dir = ctx.args.absolute_working_dir orelse ""; return bun.shell.Interpreter.initAndRunFromFile(ctx, mini, entry_path); } diff --git a/src/bun.js/event_loop/MiniEventLoop.zig b/src/bun.js/event_loop/MiniEventLoop.zig index bcca2bf428..db8723d6a2 100644 --- a/src/bun.js/event_loop/MiniEventLoop.zig +++ b/src/bun.js/event_loop/MiniEventLoop.zig @@ -40,7 +40,7 @@ pub threadlocal var global: *MiniEventLoop = undefined; pub const ConcurrentTaskQueue = UnboundedQueue(AnyTaskWithExtraContext, .next); -pub fn initGlobal(env: ?*bun.DotEnv.Loader) *MiniEventLoop { +pub fn initGlobal(env: ?*bun.DotEnv.Loader, cwd: ?[]const u8) *MiniEventLoop { if (globalInitialized) return global; const loop = MiniEventLoop.init(bun.default_allocator); global = bun.handleOom(bun.default_allocator.create(MiniEventLoop)); @@ -54,6 +54,22 @@ pub fn initGlobal(env: ?*bun.DotEnv.Loader) *MiniEventLoop { loader.* = bun.DotEnv.Loader.init(map, bun.default_allocator); break :env_loader loader; }; + + // Set top_level_dir from provided cwd or get current working directory + if (cwd) |dir| { + global.top_level_dir = dir; + } else if (global.top_level_dir.len == 0) { + var buf: bun.PathBuffer = undefined; + switch (bun.sys.getcwd(&buf)) { + .result => |p| { + global.top_level_dir = bun.default_allocator.dupe(u8, p) catch ""; + }, + .err => { + global.top_level_dir = ""; + }, + } + } + globalInitialized = true; return global; } diff --git a/src/cli/bunx_command.zig b/src/cli/bunx_command.zig index e563d0bc73..f679eb59b1 100644 --- a/src/cli/bunx_command.zig +++ b/src/cli/bunx_command.zig @@ -756,7 +756,7 @@ pub const BunxCommand = struct { .stdin = .inherit, .windows = if (Environment.isWindows) .{ - .loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(this_transpiler.env)), + .loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(this_transpiler.env, null)), }, }) catch |err| { Output.prettyErrorln("error: bunx failed to install {s} due to error {s}", .{ install_param, @errorName(err) }); diff --git a/src/cli/create_command.zig b/src/cli/create_command.zig index 2fdca20a9d..1bea76377b 100644 --- a/src/cli/create_command.zig +++ b/src/cli/create_command.zig @@ -109,7 +109,7 @@ fn execTask(allocator: std.mem.Allocator, task_: string, cwd: string, _: string, .stdin = .inherit, .windows = if (Environment.isWindows) .{ - .loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null)), + .loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null, null)), }, }) catch return; } @@ -1487,7 +1487,7 @@ pub const CreateCommand = struct { .stdin = .inherit, .windows = if (Environment.isWindows) .{ - .loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null)), + .loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null, null)), }, }); _ = try process.unwrap(); diff --git a/src/cli/exec_command.zig b/src/cli/exec_command.zig index 4d14fbe6dd..5bde215948 100644 --- a/src/cli/exec_command.zig +++ b/src/cli/exec_command.zig @@ -9,9 +9,7 @@ pub const ExecCommand = struct { null, ); try bundle.runEnvLoader(false); - const mini = bun.jsc.MiniEventLoop.initGlobal(bundle.env); var buf: bun.PathBuffer = undefined; - const cwd = switch (bun.sys.getcwd(&buf)) { .result => |p| p, .err => |e| { @@ -19,6 +17,7 @@ pub const ExecCommand = struct { Global.exit(1); }, }; + const mini = bun.jsc.MiniEventLoop.initGlobal(bundle.env, cwd); const parts: []const []const u8 = &[_][]const u8{ cwd, "[eval]", diff --git a/src/cli/filter_run.zig b/src/cli/filter_run.zig index dbde2c4f6f..57d56a3d47 100644 --- a/src/cli/filter_run.zig +++ b/src/cli/filter_run.zig @@ -532,7 +532,7 @@ pub fn runScriptsWithFilter(ctx: Command.Context) !noreturn { Global.exit(1); } - const event_loop = bun.jsc.MiniEventLoop.initGlobal(this_transpiler.env); + const event_loop = bun.jsc.MiniEventLoop.initGlobal(this_transpiler.env, null); const shell_bin: [:0]const u8 = if (Environment.isPosix) RunCommand.findShell(this_transpiler.env.get("PATH") orelse "", fsinstance.top_level_dir) orelse return error.MissingShell else diff --git a/src/cli/pm_version_command.zig b/src/cli/pm_version_command.zig index 742fddb01f..21deb456cb 100644 --- a/src/cli/pm_version_command.zig +++ b/src/cli/pm_version_command.zig @@ -461,7 +461,7 @@ pub const PmVersionCommand = struct { .cwd = cwd, .envp = null, .windows = if (Environment.isWindows) .{ - .loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null)), + .loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null, null)), }, }) catch |err| { Output.errGeneric("Failed to spawn git process: {s}", .{@errorName(err)}); @@ -494,7 +494,7 @@ pub const PmVersionCommand = struct { .cwd = cwd, .envp = null, .windows = if (Environment.isWindows) .{ - .loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null)), + .loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null, null)), }, }) catch |err| { Output.err(err, "Failed to spawn git process", .{}); @@ -541,7 +541,7 @@ pub const PmVersionCommand = struct { .stdin = .ignore, .envp = null, .windows = if (Environment.isWindows) .{ - .loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null)), + .loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null, null)), }, }) catch |err| { Output.errGeneric("Git add failed: {s}", .{@errorName(err)}); @@ -575,7 +575,7 @@ pub const PmVersionCommand = struct { .stdin = .ignore, .envp = null, .windows = if (Environment.isWindows) .{ - .loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null)), + .loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null, null)), }, }) catch |err| { Output.errGeneric("Git commit failed: {s}", .{@errorName(err)}); @@ -606,7 +606,7 @@ pub const PmVersionCommand = struct { .stdin = .ignore, .envp = null, .windows = if (Environment.isWindows) .{ - .loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null)), + .loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null, null)), }, }) catch |err| { Output.errGeneric("Git tag failed: {s}", .{@errorName(err)}); diff --git a/src/cli/run_command.zig b/src/cli/run_command.zig index 35d431e8ef..23fb3f5ff7 100644 --- a/src/cli/run_command.zig +++ b/src/cli/run_command.zig @@ -246,7 +246,7 @@ pub const RunCommand = struct { } if (!use_system_shell) { - const mini = bun.jsc.MiniEventLoop.initGlobal(env); + const mini = bun.jsc.MiniEventLoop.initGlobal(env, cwd); const code = bun.shell.Interpreter.initAndRunFromSource(ctx, mini, name, copy_script.items, cwd) catch |err| { if (!silent) { Output.prettyErrorln("error: Failed to run script {s} due to error {s}", .{ name, @errorName(err) }); @@ -294,7 +294,7 @@ pub const RunCommand = struct { .ipc = ipc_fd, .windows = if (Environment.isWindows) .{ - .loop = jsc.EventLoopHandle.init(jsc.MiniEventLoop.initGlobal(env)), + .loop = jsc.EventLoopHandle.init(jsc.MiniEventLoop.initGlobal(env, null)), }, }) catch |err| { if (!silent) { @@ -467,7 +467,7 @@ pub const RunCommand = struct { .use_execve_on_macos = silent, .windows = if (Environment.isWindows) .{ - .loop = jsc.EventLoopHandle.init(jsc.MiniEventLoop.initGlobal(env)), + .loop = jsc.EventLoopHandle.init(jsc.MiniEventLoop.initGlobal(env, null)), }, }) catch |err| { bun.handleErrorReturnTrace(err, @errorReturnTrace()); diff --git a/src/cli/upgrade_command.zig b/src/cli/upgrade_command.zig index 202888d377..a372098a8b 100644 --- a/src/cli/upgrade_command.zig +++ b/src/cli/upgrade_command.zig @@ -601,7 +601,7 @@ pub const UpgradeCommand = struct { .stdin = .inherit, .windows = if (Environment.isWindows) .{ - .loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null)), + .loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null, null)), }, }) catch |err| { Output.prettyErrorln("error: Failed to spawn Expand-Archive on {s} due to error {s}", .{ tmpname, @errorName(err) }); diff --git a/src/create/SourceFileProjectGenerator.zig b/src/create/SourceFileProjectGenerator.zig index 761204aa66..68c5af8636 100644 --- a/src/create/SourceFileProjectGenerator.zig +++ b/src/create/SourceFileProjectGenerator.zig @@ -248,7 +248,7 @@ pub fn generateFiles(allocator: std.mem.Allocator, entry_point: string, dependen .stdin = .inherit, .windows = if (Environment.isWindows) .{ - .loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null)), + .loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null, null)), }, }) catch |err| { Output.err(err, "failed to install dependencies", .{}); @@ -361,7 +361,7 @@ pub fn generateFiles(allocator: std.mem.Allocator, entry_point: string, dependen .stdin = .inherit, .windows = if (Environment.isWindows) .{ - .loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null)), + .loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null, null)), }, }) catch |err| { Output.err(err, "failed to start app", .{}); diff --git a/src/http/HTTPThread.zig b/src/http/HTTPThread.zig index d6946798e8..23f0514621 100644 --- a/src/http/HTTPThread.zig +++ b/src/http/HTTPThread.zig @@ -198,7 +198,7 @@ pub fn onStart(opts: InitOpts) void { bun.http.default_arena = Arena.init(); bun.http.default_allocator = bun.http.default_arena.allocator(); - const loop = bun.jsc.MiniEventLoop.initGlobal(null); + const loop = bun.jsc.MiniEventLoop.initGlobal(null, null); if (Environment.isWindows) { _ = std.process.getenvW(comptime bun.strings.w("SystemRoot")) orelse { diff --git a/src/open.zig b/src/open.zig index a4249d6138..d2878e2e75 100644 --- a/src/open.zig +++ b/src/open.zig @@ -25,7 +25,7 @@ pub fn openURL(url: stringZ) void { .stdin = .inherit, .windows = if (Environment.isWindows) .{ - .loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null)), + .loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null, null)), }, }) catch break :maybe_fallback) { // don't fallback: diff --git a/src/shell/IO.zig b/src/shell/IO.zig index 556ed894ad..6be1a9d886 100644 --- a/src/shell/IO.zig +++ b/src/shell/IO.zig @@ -142,16 +142,14 @@ pub const OutKind = union(enum) { .capture = .{ .buf = cap, }, + } else if (val.writer.fd.get()) |fd| .{ + // We have a valid fd that hasn't been moved to libuv + .fd = fd, } else .{ - // Windows notes: - // Since `val.writer.fd` is `MovableFD`, it could - // technically be moved to libuv for ownership. - // - // But since this file descriptor never going to be touched by this - // process, except to hand off to the subprocess when we - // spawn it, we don't really care if the file descriptor - // ends up being invalid. - .fd = val.writer.fd.get().?, + // On Windows, the fd might have been moved to libuv + // In this case, the subprocess should inherit the stdio + // since libuv is already managing it + .inherit = {}, }; }, .pipe => .pipe, diff --git a/test/regression/issue/22650-shell-crash.test.ts b/test/regression/issue/22650-shell-crash.test.ts new file mode 100644 index 0000000000..4feb8f1c71 --- /dev/null +++ b/test/regression/issue/22650-shell-crash.test.ts @@ -0,0 +1,24 @@ +import { expect, test } from "bun:test"; +import { bunEnv, bunExe } from "harness"; + +test("issue #22650 - shell crash with && operator followed by external command", async () => { + // Minimal reproduction: echo && + // This triggers the crash because after the first command succeeds, + // the shell tries to spawn an external process but top_level_dir is not set + await using proc = Bun.spawn({ + cmd: [bunExe(), "exec", "echo test && node --version"], + env: bunEnv, + stderr: "pipe", + stdout: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + // Should not have any errors + expect(stderr).toBe(""); + + // Should execute both commands successfully + expect(stdout).toContain("test"); + expect(stdout).toMatch(/v\d+\.\d+\.\d+/); // Node version pattern + expect(exitCode).toBe(0); +});