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
This commit is contained in:
mastertyko 2026-03-26 16:15:21 +01:00 committed by GitHub
parent 6f3275ff59
commit 14e5264d52
3 changed files with 13 additions and 7 deletions

View file

@ -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;

View file

@ -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", () => {

View file

@ -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", () => {