diff --git a/src/shared/pid-alive.test.ts b/src/shared/pid-alive.test.ts index 9f744f71f5a2..c3f1f15d1c3c 100644 --- a/src/shared/pid-alive.test.ts +++ b/src/shared/pid-alive.test.ts @@ -42,6 +42,45 @@ describe("isPidAlive", () => { expect(isPidAlive(Number.POSITIVE_INFINITY)).toBe(false); }); + it("returns true when process probing reports EPERM", () => { + const error = Object.assign(new Error("permission denied"), { code: "EPERM" }); + vi.spyOn(process, "kill").mockImplementation(() => { + throw error; + }); + mockProcReads({ + "/proc/42/status": "Name:\tnode\nState:\tS (sleeping)\nPid:\t42\n", + }); + + expect(isPidAlive(42)).toBe(true); + expect(process["kill"]).toHaveBeenCalledWith(42, 0); + }); + + it("returns false for Linux zombies even when probing reports EPERM", async () => { + const error = Object.assign(new Error("permission denied"), { code: "EPERM" }); + vi.spyOn(process, "kill").mockImplementation(() => { + throw error; + }); + mockProcReads({ + "/proc/42/status": "Name:\tnode\nUmask:\t0022\nState:\tZ (zombie)\nTgid:\t42\nPid:\t42\n", + }); + + await withMockedPlatform("linux", async () => { + expect(isPidAlive(42)).toBe(false); + }); + + expect(process["kill"]).toHaveBeenCalledWith(42, 0); + }); + + it("returns false when process probing reports ESRCH", () => { + const error = Object.assign(new Error("missing process"), { code: "ESRCH" }); + vi.spyOn(process, "kill").mockImplementation(() => { + throw error; + }); + + expect(isPidAlive(42)).toBe(false); + expect(process["kill"]).toHaveBeenCalledWith(42, 0); + }); + it("returns false for zombie processes on Linux", async () => { const zombiePid = process.pid; diff --git a/src/shared/pid-alive.ts b/src/shared/pid-alive.ts index b36955e25dfc..68ab91086f6b 100644 --- a/src/shared/pid-alive.ts +++ b/src/shared/pid-alive.ts @@ -32,8 +32,13 @@ export function isPidAlive(pid: number): boolean { } try { process.kill(pid, 0); - } catch { - return false; + } catch (err) { + // EPERM means the PID exists but we cannot signal it. Treat that as a + // successful existence probe, then still apply the Linux zombie check. + // Keep parity with isPidDefinitelyDead (EPERM is not "definitely dead"). + if ((err as NodeJS.ErrnoException).code !== "EPERM") { + return false; + } } if (isZombieProcess(pid)) { return false;