child_process: latch exec/execFile maxBuffer overflow so truncated output never exceeds the cap
What
exec/execFile with a fast writer and a maxBuffer cap could hand the callback a stdout/stderr up to ~3x the cap (still with ERR_CHILD_PROCESS_STDIO_MAXBUFFER). Node and Bun 1.4.0 both truncate to exactly maxBuffer.
Repro
import { execFile } from "node:child_process";
execFile("head", ["-c", "4194304", "/dev/zero"], { maxBuffer: 65536, encoding: "buffer" }, (err, stdout) => {
console.log(err?.code, stdout.length); // ERR_CHILD_PROCESS_STDIO_MAXBUFFER 131072 (expected 65536)
});
Cause
027716a143bc ("stdin: apply highwater backpressure to the pipe FileReader source") made the child's stdout FileReader deliver ~64 KiB chunks with the next one already queued, so a second data event can fire after execFile's kill()/stdout.destroy(). The onData handler has no latch; on that second event totalLen - length is already past maxBuffer, so
const truncatedLen = maxBuffer - (totalLen - length); // negative
$arrayPush(_buffer, chunk.slice(0, truncatedLen)); // slice(0, -k) keeps chunk.length - k bytes
appends past the cap and calls kill() again. Node has the same arithmetic but its destroy() stops further data events; this was a latent bug in the JS handler exposed by the chunking change.
Fix
Add a per-stream maxBufferTripped latch so the handler drops every chunk after the first overflow (error and kill() fire once), and clamp truncatedLen at zero.
Verification
New test in test/js/node/child_process/child-process-exec.test.ts spawns head -c 4194304 /dev/zero ten times concurrently for each of encoding: "buffer" and "utf8" and asserts every callback receives { code: "ERR_CHILD_PROCESS_STDIO_MAXBUFFER", len: 65536 }.
Without the fix (src/ stashed): 10/10 runs over-capture (126976..131072 bytes) in both encodings.
With the fix: 10/10 exact, all 13 tests in the file pass.
no test proof · iteration 2 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/node/child_process/child-process-exec.test.ts