fix: address Copilot review round 3 (#2339)

- The spawn itself — spawnSpec and the synchronous argument validation in
  child_process.spawn — now sits inside the try whose finally releases the
  lease, so a command that fails before it starts cannot strand the lock
  until stale takeover. Tested with a non-array args value.
- A genuine release failure no longer falls through to the "released
  after" line; the lock may still be blocking the queue, and the message
  that says so is the last word.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PsmUAZ6aLUAFqenjJKSSRp
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-09-12 22:42:57 -04:00
commit d3e5432532

View file

@ -274,6 +274,27 @@ test("a command that cannot be spawned still releases the lease", async () => {
assert.ok(!existsSync(leaseTarget(dir)));
});
test("a command that fails synchronously before it starts still releases the lease", async () => {
const dir = freshDir();
const { log } = collectLog();
// `spawn` validates its arguments synchronously: a non-array `args` throws
// before any child exists, the same class as `winShellArgs` refusing a `%`
// on Windows. That throw must not strand the lock until stale takeover.
await assert.rejects(
runUnderLease({
command: process.execPath,
args: "-e",
dir,
log,
stdio: "ignore",
pollMs: 25,
}),
{ code: "ERR_INVALID_ARG_TYPE" },
);
assert.ok(!existsSync(lockPathOf(dir)));
assert.ok(!existsSync(leaseTarget(dir)));
});
test("a lock replaced mid-run (stale takeover) is the winner's, and is left alone", async () => {
const dir = freshDir();
const { lines, log } = collectLog();