From 4f6930f5694ecdccd0d14ca16ce59c3e30127766 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 28 Aug 2026 06:53:00 -0700 Subject: [PATCH] fix(daemon): only announce the pid-publish degrade once it actually published The warning sat above the degraded exclusive write, but that write can itself throw EEXIST: a daemon losing an ownership race on a hard-link-incapable volume announced 'torn-write protection is off for this record' for a record it never wrote. Announce after the write lands, so the claim is true when it is made. Also pin describeErrnoCode's no-code branch, which existed for exactly the exotic-volume error the degrade handles and was never exercised. --- .../daemon-pid-publish-atomicity.test.ts | 22 +++++++++++++++++++ src/main/daemon/daemon-spawner.ts | 5 ++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/main/daemon/daemon-pid-publish-atomicity.test.ts b/src/main/daemon/daemon-pid-publish-atomicity.test.ts index 086f5432ed2..b6316da1ac1 100644 --- a/src/main/daemon/daemon-pid-publish-atomicity.test.ts +++ b/src/main/daemon/daemon-pid-publish-atomicity.test.ts @@ -170,6 +170,28 @@ describe('publishDaemonPidFile atomicity', () => { expect(entries).toEqual([]) }) + it('stays silent when the degraded write itself loses the ownership race', () => { + // Same catch arm as the degrade, but nothing was published: announcing lost + // torn-write protection for a record that was never written is a false alarm. + const entries: string[] = [] + publishDaemonPidFile(pidPath, { pid: 1, startedAtMs: 5, launchNonce: 'other' }) + fsFaults.linkError = Object.assign(new Error('injected ENOTSUP'), { code: 'ENOTSUP' }) + + expect(() => + publishDaemonPidFile(pidPath, RECORD, { log: (event) => entries.push(event) }) + ).toThrow(/EEXIST/) + expect(warnSpy).not.toHaveBeenCalled() + expect(entries).toEqual([]) + }) + + it('names the errno even when the link error carries none', () => { + fsFaults.linkError = new Error('injected link failure with no code') + + publishDaemonPidFile(pidPath, RECORD) + + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('no errno')) + }) + it('keeps exclusive-create semantics on the degraded path too', () => { publishDaemonPidFile(pidPath, { pid: 1, startedAtMs: 5, launchNonce: 'other' }) fsFaults.linkError = Object.assign(new Error('injected ENOTSUP'), { code: 'ENOTSUP' }) diff --git a/src/main/daemon/daemon-spawner.ts b/src/main/daemon/daemon-spawner.ts index 4892e758b38..6212f1d46c2 100644 --- a/src/main/daemon/daemon-spawner.ts +++ b/src/main/daemon/daemon-spawner.ts @@ -204,6 +204,10 @@ export function publishDaemonPidFile( // instead of trading a torn-write window for no pid record at all. Warn because the // degrade is otherwise invisible: an operator on such a volume (network mount, // container overlay) should be able to tell the atomic path is not in use. + writeFileSync(pidPath, serializeDaemonPidFile(pidFile), { mode: 0o600, flag: 'wx' }) + // Announced only once the degraded write has actually landed: this same arm is + // reached by a lost ownership race, where the write throws EEXIST and nothing + // was published — claiming torn-write exposure for that would be a lie. // Both channels, matching daemon-endpoint-lifecycle.ts: inside the detached // daemon stdio is 'ignore' and its startup stderr pipe is destroyed once it is // up, so console alone reaches nobody on the path this warning exists for. @@ -212,7 +216,6 @@ export function publishDaemonPidFile( `[daemon] pid-record hard-link publish failed (${describeErrnoCode(error)}); ` + 'degrading to the non-atomic exclusive write — torn-write protection is off for this record' ) - writeFileSync(pidPath, serializeDaemonPidFile(pidFile), { mode: 0o600, flag: 'wx' }) } } finally { try {