Bun.spawn: truncate stdout/stderr when redirected to Bun.file(path)

Closed
RO
16d ago
+48-2

What does this PR do?

Bun.spawn({ stdout: Bun.file(path) }) (and spawnSync, and stderr:) opened the destination with O_WRONLY|O_CREAT but without O_TRUNC. If the file already existed and the child wrote fewer bytes than were already there, the stale tail of the old content was left in place.

writeFileSync(p, "OLD-CONTENT-0123456789ABCDEF");
await Bun.spawn([bunExe(), "-e", "process.stdout.write('NEW')"], { stdout: Bun.file(p) }).exited;
readFileSync(p, "utf8");
// before: "NEW-CONTENT-0123456789ABCDEF"
// after:  "NEW"

Every other "write these bytes to this path" surface already truncates: Bun.write(path, data), Bun.$\cmd > ${path}`, and fs.openSync(path, 'w')`. This brings the spawn redirect in line.

How did you verify your code works?

New tests in test/js/bun/spawn/spawn.test.ts for spawn stdout/stderr and spawnSync stdout. Each pre-fills the destination with a longer string and asserts the exact short output afterwards.

(fail -> pass) gcTick > spawnSync > Bun.file() as stdout truncates the destination
(fail -> pass) gcTick > spawn > Bun.file() as stdout truncates the destination
(fail -> pass) gcTick > spawn > Bun.file() as stderr truncates the destination

Existing Bun.file() works as stdin and Bun.file(path) at index >= 3 is readable in the child still pass: stdin stays O_RDONLY and extra stdio slots stay O_RDWR|O_CREAT (no truncation), since the established use case there is reading from the file in the child.

Fix applied on both the posix_spawn path (src/spawn_sys/spawn_process.rs) and the Windows libuv path (src/spawn/process.rs).


no test proof · iteration 3 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/bun/spawn/spawn.test.ts