diff --git a/src/process/ExecFileOutput.test.ts b/src/process/ExecFileOutput.test.ts index 6a5eb5b2e3..f4d690e336 100644 --- a/src/process/ExecFileOutput.test.ts +++ b/src/process/ExecFileOutput.test.ts @@ -123,8 +123,8 @@ describe.skipIf(process.platform === "win32")( expect(await readFile(stopped, "utf8")).toBe("SIGINT"); }); - it.each([-1, Number.NaN, 1.5, Number.POSITIVE_INFINITY])( - "refuses a malformed timeout (%s) before starting the command, as execFile does", + it.each([-1, Number.NaN, 1.5, Number.POSITIVE_INFINITY, 2 ** 53])( + "refuses a malformed timeout (%s) in both modes before starting the command", async (timeout) => { // Starting this command would fail with ENOENT instead. await expect( diff --git a/src/process/ExecFileOutput.ts b/src/process/ExecFileOutput.ts index 94a73aa9f6..dd6aaae620 100644 --- a/src/process/ExecFileOutput.ts +++ b/src/process/ExecFileOutput.ts @@ -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;