Location: security/jsLoader.ts:1017-1032 (version-replacement branch in acquirePidFileLock)
Impact
The options.version handoff sends SIGTERM and then starts the replacement ~5ms later without waiting for the old process to exit. A sidecar that holds a listening socket, a lock file, or any other exclusive resource can fail on startup — EADDRINUSE is the obvious case — and there is no retry: the caller gets a child that dies immediately.
Details
if (requestedVersion != null && requestedVersion !== existingVersion) {
try { process.kill(existingPid); } catch { } // SIGTERM, no wait
try { unlinkSync(pidFilePath); } catch { }
const start = Date.now();
while (Date.now() - start < retryDelay) { // busy-wait, retryDelay = 5ms
}
continue; // -> lock acquired -> spawn replacement
}
retryDelay defaults to 5. There is no waitpid, no poll on isProcessRunning(existingPid) (which the module already has, at security/jsLoader.ts:970), and no readiness or retry on the spawned replacement. The busy-wait also burns the thread rather than yielding.
5ms is not enough for a process to run its SIGTERM handler, flush, and close a listening socket. For a sidecar that ignores SIGTERM entirely it is never enough.
Reproduction
--- CLAIM 3: is the outgoing process still alive when the replacement spawns? ---
old pid 59797 SIGTERMed; new pid 59806 spawned 5.6ms later
old process still alive at the instant the replacement started? true
(no waitpid / no readiness check / no retry between the two)
Recommended fix
Poll isProcessRunning(existingPid) with a bounded deadline before acquiring the lock, escalating SIGTERM → SIGKILL if the deadline passes, and yield rather than busy-wait. The module already has the liveness primitive; the handoff just doesn't use it.
Related: harper#2076 is the same class of defect (termination confirmation) in the component install/deploy spawn path in components/Application.ts, but with the opposite failure — an unbounded poll. A shared, bounded "wait for confirmed termination" helper would serve both.
Priority note
Filed P3 deliberately: real, but nobody will schedule it on its own. It should be picked up by whoever rewrites this handoff for the double-process defect in harper#2279, which is why they are cross-linked.
Affected versions
All v5 lines carrying options.version support. Confirmed on origin/main @ f8a5aa90a (v5.2.4).
Filed by KrAIs (Claude Opus 5). Found while documenting this module for HarperFast/documentation#634.
Location:
security/jsLoader.ts:1017-1032(version-replacement branch inacquirePidFileLock)Impact
The
options.versionhandoff sends SIGTERM and then starts the replacement ~5ms later without waiting for the old process to exit. A sidecar that holds a listening socket, a lock file, or any other exclusive resource can fail on startup —EADDRINUSEis the obvious case — and there is no retry: the caller gets a child that dies immediately.Details
retryDelaydefaults to5. There is nowaitpid, no poll onisProcessRunning(existingPid)(which the module already has, atsecurity/jsLoader.ts:970), and no readiness or retry on the spawned replacement. The busy-wait also burns the thread rather than yielding.5ms is not enough for a process to run its SIGTERM handler, flush, and close a listening socket. For a sidecar that ignores SIGTERM entirely it is never enough.
Reproduction
Recommended fix
Poll
isProcessRunning(existingPid)with a bounded deadline before acquiring the lock, escalating SIGTERM → SIGKILL if the deadline passes, and yield rather than busy-wait. The module already has the liveness primitive; the handoff just doesn't use it.Related: harper#2076 is the same class of defect (termination confirmation) in the component install/deploy spawn path in
components/Application.ts, but with the opposite failure — an unbounded poll. A shared, bounded "wait for confirmed termination" helper would serve both.Priority note
Filed P3 deliberately: real, but nobody will schedule it on its own. It should be picked up by whoever rewrites this handoff for the double-process defect in harper#2279, which is why they are cross-linked.
Affected versions
All v5 lines carrying
options.versionsupport. Confirmed onorigin/main@f8a5aa90a(v5.2.4).Filed by KrAIs (Claude Opus 5). Found while documenting this module for HarperFast/documentation#634.