fix(process): apply one timeout rule to both execFileOutput modes

Node's execFile timeout contract differs by supported runtime: Node 22
and 24 accept any non-negative integer (their timers clamp anything
above 2^31-1 to 1 ms), while Node 26 caps it at Number.MAX_SAFE_INTEGER.
Mirroring either left stoppable and plain commands disagreeing on one
runtime. Validate a safe non-negative integer before spawning in both
modes, so every supported runtime gives the same answer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kaoru0822andClaude Opus 5.5 committed 2026-10-09 08:22:09 +00:00
1 parent 59c91c88fe
commit 3d328193d6
2 files changed
+7 -8

No files matched your search

+5 -6
View File
@@ -54,8 +54,10 @@ const timeoutError = (
};
/**
* Node's execFile contract for `timeout`. A stoppable command keeps its own
* deadline, so it must refuse the values execFile would refuse.
* The `timeout` contract for both modes, checked before spawning. A stoppable
* command keeps its own deadline, so execFile never validates it. Node 26's
* execFile also caps it at Number.MAX_SAFE_INTEGER; Node 22 and 24 accept
* larger integers, which their timers then clamp to 1 ms.
*/
const invalidTimeout = (timeout: number): RangeError | undefined => {
if (Number.isSafeInteger(timeout) && timeout >= 0) return undefined;
@@ -106,10 +108,7 @@ export const execFileOutput = (
): Promise<{ readonly stdout: string; readonly stderr: string }> =>
new Promise((resolve, reject) => {
const { stopSignal, signal, timeout, ...execOptions } = options;
const invalid =
stopSignal === undefined || timeout === undefined
? undefined
: invalidTimeout(timeout);
const invalid = timeout === undefined ? undefined : invalidTimeout(timeout);
if (invalid !== undefined) {
reject(invalid);
return;