diff --git a/extensions/background-terminals/src/manager.ts b/extensions/background-terminals/src/manager.ts index a8639400..844dcb28 100644 --- a/extensions/background-terminals/src/manager.ts +++ b/extensions/background-terminals/src/manager.ts @@ -395,11 +395,9 @@ export async function signalWindowsProcessTree( : attempt.outcome === "timed_out" ? `taskkill timed out after ${attempt.timeoutMs}ms; helper ${attempt.helperClosed ? "closed after SIGKILL" : `did not close within an additional ${attempt.helperCloseTimeoutMs}ms`}` : `taskkill exited ${attempt.exitCode ?? "without a code"}${attempt.signal ? ` (${attempt.signal})` : ""}`; - if ( - attempt.outcome === "timed_out" && - !attempt.helperClosed && - !targetExited() - ) { + // Closing the helper or observing the shell exit does not prove that all + // descendants exited. Preserve uncertainty instead of killing only the shell. + if (attempt.outcome === "timed_out") { return { outcome: "unresolved", detail }; } // A failed graceful taskkill must leave the shell PID alive for the diff --git a/tests/extensions/background-terminals/manager.test.ts b/tests/extensions/background-terminals/manager.test.ts index b37e617c..8b9114da 100644 --- a/tests/extensions/background-terminals/manager.test.ts +++ b/tests/extensions/background-terminals/manager.test.ts @@ -204,6 +204,38 @@ test("Windows taskkill timeout waits for the stopped helper to close", async () }); }); +for (const signal of ["SIGTERM", "SIGKILL"] as const) { + for (const shellExitsDuringTimeout of [false, true]) { + test(`Windows ${signal} taskkill timeout stays unresolved when shell exit is ${shellExitsDuringTimeout}`, async () => { + let targetExited = false; + const killer = new EventEmitter() as ChildProcess; + killer.kill = () => { + targetExited = shellExitsDuringTimeout; + queueMicrotask(() => killer.emit("close", null, "SIGKILL")); + return true; + }; + const signals: Array = []; + + const result = await signalWindowsProcessTree( + { + pid: 47, + kill(signal) { + signals.push(signal); + return true; + }, + }, + signal, + () => targetExited, + () => killer, + ); + + assert.equal(result.outcome, "unresolved"); + assert.match(result.detail, /helper closed after SIGKILL/); + assert.deepEqual(signals, []); + }); + } +} + test("Windows taskkill helper close has a second explicit bound", async () => { const killer = new EventEmitter() as ChildProcess; killer.kill = () => true;