From 77b6bb62db948d6dd23188d7610ff705852bc7c7 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Mon, 18 May 2026 17:55:06 -0400 Subject: [PATCH] fix(state): acquireStateLock last-retry now re-acquires before proceeding (#3711) --- .changeset/mellow-hawks-greet.md | 5 +++++ get-shit-done/bin/lib/state.cjs | 13 ++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 .changeset/mellow-hawks-greet.md diff --git a/.changeset/mellow-hawks-greet.md b/.changeset/mellow-hawks-greet.md new file mode 100644 index 000000000..f988e2461 --- /dev/null +++ b/.changeset/mellow-hawks-greet.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 3705 +--- +Repair Windows-only TOCTOU regression in acquireStateLock — last-retry stale-lock recovery now re-acquires the lock atomically before proceeding, so concurrent state writes to different fields both persist (fixes main CI red on locking-bugs regression test). diff --git a/get-shit-done/bin/lib/state.cjs b/get-shit-done/bin/lib/state.cjs index e0b2c284e..84e9828eb 100644 --- a/get-shit-done/bin/lib/state.cjs +++ b/get-shit-done/bin/lib/state.cjs @@ -942,7 +942,18 @@ function acquireStateLock(statePath) { } catch { /* lock was released between check — retry */ } if (i === maxRetries - 1) { - try { fs.unlinkSync(lockPath); } catch {} + // Stale-lock recovery: delete the lock and do one final acquisition + // attempt. Returning lockPath without holding the lock (the previous + // behaviour) allowed two concurrent processes to both "acquire" a + // phantom lock, breaking mutual exclusion on Windows-24 where slower + // process spawn means concurrent writers overlap for longer (#3705). + try { fs.unlinkSync(lockPath); } catch { /* already gone — proceed */ } + try { + const fd = fs.openSync(lockPath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY); + fs.writeSync(fd, String(process.pid)); + fs.closeSync(fd); + _heldStateLocks.add(lockPath); + } catch { /* another process raced us — proceed without lock as last resort */ } return lockPath; } const jitter = Math.floor(Math.random() * 50);