From c3c1f9df54c711f72cd7cdbbc58c23a46fd6236e Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Sat, 28 Mar 2026 16:37:29 +0530 Subject: [PATCH] fix(process): wait for windows exit code settlement --- src/process/exec.ts | 15 ++++++++++++++- src/process/exec.windows.test.ts | 20 ++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/process/exec.ts b/src/process/exec.ts index 94bb80aa3df..4223d873f2f 100644 --- a/src/process/exec.ts +++ b/src/process/exec.ts @@ -330,7 +330,7 @@ export async function runCommandWithTimeout( child.stderr?.destroy(); }, 250); }); - child.on("close", (code, signal) => { + const resolveFromClose = (code: number | null, signal: NodeJS.Signals | null) => { if (settled) { return; } @@ -363,6 +363,19 @@ export async function runCommandWithTimeout( termination, noOutputTimedOut, }); + }; + child.on("close", (code, signal) => { + if ( + childExitState == null && + code == null && + signal == null && + child.exitCode == null && + child.signalCode == null + ) { + setImmediate(() => resolveFromClose(code, signal)); + return; + } + resolveFromClose(code, signal); }); }); } diff --git a/src/process/exec.windows.test.ts b/src/process/exec.windows.test.ts index 4f0d4b290af..26a4f0c2a22 100644 --- a/src/process/exec.windows.test.ts +++ b/src/process/exec.windows.test.ts @@ -31,6 +31,7 @@ function createMockChild(params?: { closeCode?: number | null; closeSignal?: NodeJS.Signals | null; exitCode?: number | null; + exitCodeAfterClose?: number | null; signal?: NodeJS.Signals | null; }): MockChild { const child = new EventEmitter() as MockChild; @@ -47,6 +48,11 @@ function createMockChild(params?: { child.killed = false; queueMicrotask(() => { child.emit("close", params?.closeCode ?? 0, params?.closeSignal ?? params?.signal ?? null); + if (params?.exitCodeAfterClose !== undefined) { + setImmediate(() => { + child.exitCode = params.exitCodeAfterClose ?? null; + }); + } }); return child; } @@ -117,6 +123,20 @@ describe("windows command wrapper behavior", () => { } }); + it("waits one tick when Windows close reports null before exitCode settles", async () => { + const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32"); + const child = createMockChild({ closeCode: null, exitCode: null, exitCodeAfterClose: 0 }); + + spawnMock.mockImplementation(() => child); + + try { + const result = await runCommandWithTimeout(["npm", "--version"], { timeoutMs: 1000 }); + expect(result.code).toBe(0); + } finally { + platformSpy.mockRestore(); + } + }); + it("uses cmd.exe wrapper with windowsVerbatimArguments in runExec for .cmd shims", async () => { const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32"); const expectedComSpec = process.env.ComSpec ?? "cmd.exe";