From 40da0823497f68de3ec834f596ced7eeecadfd5f Mon Sep 17 00:00:00 2001 From: robobun Date: Sun, 12 Oct 2025 05:40:02 -0700 Subject: [PATCH] fix(shell): handle UV_ENOTCONN gracefully in shell subprocess (#23520) --- src/shell/subproc.zig | 15 +++- .../issue/23316-long-path-spawn-shell.test.ts | 85 +++++++++++++++++++ 2 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 test/regression/issue/23316-long-path-spawn-shell.test.ts diff --git a/src/shell/subproc.zig b/src/shell/subproc.zig index dd308b13e7..39fd6868da 100644 --- a/src/shell/subproc.zig +++ b/src/shell/subproc.zig @@ -880,18 +880,27 @@ pub const ShellSubprocess = struct { } if (subprocess.stdin == .buffer) { - subprocess.stdin.buffer.start().assert(); + if (subprocess.stdin.buffer.start().asErr()) |err| { + _ = subprocess.tryKill(@intFromEnum(bun.SignalCode.SIGTERM)); + return .{ .err = .{ .sys = err.toShellSystemError() } }; + } } if (subprocess.stdout == .pipe) { - subprocess.stdout.pipe.start(subprocess, event_loop).assert(); + if (subprocess.stdout.pipe.start(subprocess, event_loop).asErr()) |err| { + _ = subprocess.tryKill(@intFromEnum(bun.SignalCode.SIGTERM)); + return .{ .err = .{ .sys = err.toShellSystemError() } }; + } if (!spawn_args.lazy and subprocess.stdout == .pipe) { subprocess.stdout.pipe.readAll(); } } if (subprocess.stderr == .pipe) { - subprocess.stderr.pipe.start(subprocess, event_loop).assert(); + if (subprocess.stderr.pipe.start(subprocess, event_loop).asErr()) |err| { + _ = subprocess.tryKill(@intFromEnum(bun.SignalCode.SIGTERM)); + return .{ .err = .{ .sys = err.toShellSystemError() } }; + } if (!spawn_args.lazy and subprocess.stderr == .pipe) { subprocess.stderr.pipe.readAll(); diff --git a/test/regression/issue/23316-long-path-spawn-shell.test.ts b/test/regression/issue/23316-long-path-spawn-shell.test.ts new file mode 100644 index 0000000000..412a22ccf6 --- /dev/null +++ b/test/regression/issue/23316-long-path-spawn-shell.test.ts @@ -0,0 +1,85 @@ +import { $ } from "bun"; +import { expect, test } from "bun:test"; +import { bunExe, isWindows, tempDir } from "harness"; +import { join } from "path"; + +test("shell should handle cwd paths >= MAX_PATH on Windows", async () => { + if (!isWindows) { + return; + } + + using dir = tempDir("long-path-shell", {}); + + // Create a deeply nested directory structure that exceeds MAX_PATH (260 chars) + const segments: string[] = []; + let currentPath = String(dir); + let totalLength = currentPath.length; + + // Keep adding directory segments until we exceed MAX_PATH + let i = 0; + while (totalLength < 280) { + const segment = `dir${i.toString().padStart(3, "0")}`; + segments.push(segment); + totalLength += segment.length + 1; // +1 for the path separator + i++; + } + + // Create the nested directory structure + let deepPath = String(dir); + for (const segment of segments) { + deepPath = join(deepPath, segment); + await Bun.write(join(deepPath, ".keep"), ""); + } + + console.log(`Created deep path (length: ${deepPath.length}): ${deepPath}`); + expect(deepPath.length).toBeGreaterThanOrEqual(260); + + // This should either: + // 1. Succeed and spawn the process + // 2. Fail gracefully with an error (not panic with UV_ENOTCONN) + let err; + try { + await $`${bunExe()} --version`.cwd(deepPath).quiet(); + } catch (e) { + err = e; + } + expect(err).toBeInstanceOf(Error); +}); + +test("shell should handle cwd paths with disabled 8.3 names on Windows", async () => { + if (!isWindows) { + return; + } + + using dir = tempDir("8-3-disabled-shell", { + "test.js": `console.log("hello");`, + }); + + // Create a moderately long path that would trigger GetShortPathNameW + const segments = Array.from({ length: 20 }, (_, i) => `directory_with_long_name_${i}`); + let deepPath = String(dir); + for (const segment of segments) { + deepPath = join(deepPath, segment); + await Bun.write(join(deepPath, ".keep"), ""); + } + + console.log(`Created path for 8.3 test (length: ${deepPath.length}): ${deepPath}`); + + // Attempt to copy test.js to the deep path + let err; + try { + await Bun.write(join(deepPath, "test.js"), `console.log("hello");`); + } catch (e) { + err = e; + } + expect(err).toBeInstanceOf(Error); + + // This should not panic, even if GetShortPathNameW fails + err = undefined; + try { + await $`${bunExe()} test.js`.cwd(deepPath).quiet(); + } catch (e) { + err = e; + } + expect(err).toBeInstanceOf(Error); +});