From 14e5264d5279dc5bc1a41318bf1f41e846a94bce Mon Sep 17 00:00:00 2001 From: mastertyko <11311479+mastertyko@users.noreply.github.com> Date: Thu, 26 Mar 2026 16:15:21 +0100 Subject: [PATCH] fix: isLockProcessAlive should return true for own PID (#2642) The self-PID guard in isLockProcessAlive returned false for process.pid, treating the current process as dead. This caused the doctor to delete auto.lock and .gsd.lock/ during live auto-mode sessions (via postUnitPreVerification), breaking the session lock and silently stopping auto-mode. The guard was originally added for startAuto() where a matching PID could mean a recycled PID from a prior crash. But startAuto already has its own `crashLock.pid !== process.pid` check before calling isLockProcessAlive, so the function-level guard was redundant there and harmful everywhere else. Change `pid === process.pid` to return true (alive) instead of false. Closes #2470 --- src/resources/extensions/gsd/crash-recovery.ts | 8 ++++++-- .../extensions/gsd/tests/auto-lock-creation.test.ts | 4 ++-- src/resources/extensions/gsd/tests/crash-recovery.test.ts | 8 +++++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/resources/extensions/gsd/crash-recovery.ts b/src/resources/extensions/gsd/crash-recovery.ts index 1186d5ed8..9d5caa8ef 100644 --- a/src/resources/extensions/gsd/crash-recovery.ts +++ b/src/resources/extensions/gsd/crash-recovery.ts @@ -76,12 +76,16 @@ export function readCrashLock(basePath: string): LockData | null { /** * Check whether the process that wrote the lock is still running. * Uses `process.kill(pid, 0)` which sends no signal but checks liveness. - * Returns false if the PID matches our own (recycled PID from a prior run). + * Returns true if the PID matches our own — we are the lock holder (#2470). */ export function isLockProcessAlive(lock: LockData): boolean { const pid = lock.pid; if (!Number.isInteger(pid) || pid <= 0) return false; - if (pid === process.pid) return false; + // Our own PID means WE hold this lock — we are alive. (#2470) + // Callers that need to distinguish "our lock" from "someone else's lock" + // (e.g. startAuto checking for a prior crashed session with a recycled PID) + // already guard with `crashLock.pid !== process.pid` before calling us. + if (pid === process.pid) return true; try { process.kill(pid, 0); return true; diff --git a/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts b/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts index 5189e96f0..0ff8d963e 100644 --- a/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +++ b/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts @@ -140,7 +140,7 @@ test("isLockProcessAlive returns false for dead PID", () => { assert.equal(isLockProcessAlive(lock), false, "dead PID should return false"); }); -test("isLockProcessAlive returns false for own PID (recycled)", () => { +test("#2470: isLockProcessAlive returns true for own PID (we hold the lock)", () => { const lock = { pid: process.pid, startedAt: new Date().toISOString(), @@ -148,7 +148,7 @@ test("isLockProcessAlive returns false for own PID (recycled)", () => { unitId: "M001/S01/T01", unitStartedAt: new Date().toISOString(), }; - assert.equal(isLockProcessAlive(lock), false, "own PID should return false (recycled)"); + assert.equal(isLockProcessAlive(lock), true, "own PID means we are alive — not stale (#2470)"); }); test("isLockProcessAlive returns false for invalid PID", () => { diff --git a/src/resources/extensions/gsd/tests/crash-recovery.test.ts b/src/resources/extensions/gsd/tests/crash-recovery.test.ts index 7c34599e1..39323681f 100644 --- a/src/resources/extensions/gsd/tests/crash-recovery.test.ts +++ b/src/resources/extensions/gsd/tests/crash-recovery.test.ts @@ -68,8 +68,10 @@ test("clearLock is safe when no lock exists", (t) => { // ─── isLockProcessAlive ────────────────────────────────────────────────── -test("isLockProcessAlive returns true for current process (different pid)", () => { - // Our own PID is explicitly excluded (recycled PID guard) +test("#2470: isLockProcessAlive returns true for own PID (we hold the lock)", () => { + // Own PID means we ARE the lock holder — alive, not stale. (#2470) + // Callers that need recycled-PID detection (e.g. startAuto) already + // guard with `crashLock.pid !== process.pid` before calling us. const lock: LockData = { pid: process.pid, startedAt: new Date().toISOString(), @@ -77,7 +79,7 @@ test("isLockProcessAlive returns true for current process (different pid)", () = unitId: "M001/S01/T01", unitStartedAt: new Date().toISOString(), }; - assert.equal(isLockProcessAlive(lock), false, "own PID should return false"); + assert.equal(isLockProcessAlive(lock), true, "own PID should return true — we are alive"); }); test("isLockProcessAlive returns false for dead PID", () => {