From 753a104d27101ae601990cc0dc3443e7e1bac7f9 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 28 Aug 2026 08:10:26 -0700 Subject: [PATCH] test(daemon): gate the claim reap on the errno Windows actually reports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Windows lane now runs daemon-pid-publish-claim-reap.test.ts, but its only foreign-owner gate injected EPERM — the POSIX errno. Windows reports a live but unsignalable process as EACCES (isPermissionError in win32-utils treats EACCES and EPERM as one), so the errno that lane can actually see had no gate. The fence is already correct there: it reaps only on ESRCH, so every other errno reads as alive. No production change — this pins that for EACCES too, so a later broadening to "any kill() error means dead" cannot land unnoticed and delete a live publisher's claim on Windows. --- .../daemon-pid-publish-claim-reap.test.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/main/daemon/daemon-pid-publish-claim-reap.test.ts b/src/main/daemon/daemon-pid-publish-claim-reap.test.ts index 616ee5659a9..e618dabda8b 100644 --- a/src/main/daemon/daemon-pid-publish-claim-reap.test.ts +++ b/src/main/daemon/daemon-pid-publish-claim-reap.test.ts @@ -99,6 +99,24 @@ describe('reapOrphanedDaemonPidPublishClaims', () => { expect(existsSync(foreign)).toBe(true) }) + it('keeps a claim whose owner is live but unsignalable on Windows', () => { + // Why a second errno: Windows reports a live foreign owner as EACCES, not the POSIX + // EPERM above (isPermissionError in win32-utils treats both as one). Only ESRCH reaps. + const foreign = join(dir, `daemon-v7.pid.publish-424242-${CLAIM_UUID}`) + writeFileSync(foreign, '{"pid":1') + const kill = vi.spyOn(process, 'kill').mockImplementation(() => { + throw Object.assign(new Error('permission denied'), { code: 'EACCES' }) + }) + + try { + reapOrphanedDaemonPidPublishClaims(dir) + } finally { + kill.mockRestore() + } + + expect(existsSync(foreign)).toBe(true) + }) + it('never lets an unremovable claim abort the sweep or the launch', () => { // Why: reaping is hygiene on the launch funnel, so any failure here — a Windows file // lock, a permission error, a stray directory under a claim name — must not escape