You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
shared RPC host: pruning a pointer that names a dead generation leaves a live registered generation ownerless, so handoff refuses with unknown_owner #1911
pruneDeadGenerations (#1899) removes the daemon pointer (<layout>/host.pid) when it names a dead generation, but never re-points it at a LIVE registered generation that still serves the socket. Every subsequent readHostRegistration returns undefined, so handoffHost refuses with unknown_owner and host status cannot name the current generation - while a healthy host is serving the socket.
Reproduction (observed 2026-09-21 on a production daemon dir)
Generation A dies (here: Bun SIGSEGV); the pointer still names A.
A client of an OLDER build starts generation B (writeHostRegistration writes generations/B/host.pid; the pointer write is expected but the pointer ended up naming A - the older build's registration and B's start interleaved; exact ordering held locally).
daemon handoff --json -> {"action":"refuse","reason":"unknown_owner"} although B is reachable (host statusreachable: true, generations[] lists B alive: true).
Repair that worked: writing host.pid = { layout: 2, instance_id: B, generation_dir: "generations/B", writer: <B's writer> } by hand; the next daemon handoff produced generation 1 and B began draining.
Expected (ideal state)
When the pointer is dropped (or absent) and exactly one registered generation is alive AND its record's socket is the public path AND processMatchesPidFile proves the pid, pruneDeadGenerations (or readHostRegistration) elects that generation as the pointer target instead of leaving the directory ownerless.
host status reports pointer: missing, elected: <instanceId> when it had to elect.
Regression: dead-pointer + one live registered generation -> readHostRegistration returns the live one; handoffHost proceeds.
Actual
host-generations.tspruneDeadGenerations: if (pointerRemoved) await rm(paths.pointerFile) with no election. host-daemon-registration.tsreadHostRegistration: pointer-only.
Evidence
Held locally (sanitized): daemon handoff --json refusal payload, the daemon directory listing (no host.pid, two generations/*/host.pid), the repaired pointer, and the successful handoff payload (generation: 1).
Summary
pruneDeadGenerations(#1899) removes the daemon pointer (<layout>/host.pid) when it names a dead generation, but never re-points it at a LIVE registered generation that still serves the socket. Every subsequentreadHostRegistrationreturns undefined, sohandoffHostrefuses withunknown_ownerandhost statuscannot name the current generation - while a healthy host is serving the socket.Reproduction (observed 2026-09-21 on a production daemon dir)
writeHostRegistrationwritesgenerations/B/host.pid; the pointer write is expected but the pointer ended up naming A - the older build's registration and B's start interleaved; exact ordering held locally).host statusfrom a build carrying fix(rpc): drain a superseded host generation and prune dead daemon records #1899 runspruneDeadGenerations: pointer names dead A -> pointer removed.generations/B/host.pid(pid alive, processStartTime matches) remains, nothing points at it.daemon handoff --json->{"action":"refuse","reason":"unknown_owner"}although B is reachable (host statusreachable: true,generations[]lists Balive: true).Repair that worked: writing
host.pid={ layout: 2, instance_id: B, generation_dir: "generations/B", writer: <B's writer> }by hand; the nextdaemon handoffproduced generation 1 and B began draining.Expected (ideal state)
socketis the public path ANDprocessMatchesPidFileproves the pid,pruneDeadGenerations(orreadHostRegistration) elects that generation as the pointer target instead of leaving the directory ownerless.host statusreportspointer: missing, elected: <instanceId>when it had to elect.readHostRegistrationreturns the live one;handoffHostproceeds.Actual
host-generations.tspruneDeadGenerations:if (pointerRemoved) await rm(paths.pointerFile)with no election.host-daemon-registration.tsreadHostRegistration: pointer-only.Evidence
Held locally (sanitized):
daemon handoff --jsonrefusal payload, the daemon directory listing (nohost.pid, twogenerations/*/host.pid), the repaired pointer, and the successful handoff payload (generation: 1).Related
#1893, #1899 (drain + prune), #1905 / #1906 (this incident's host fixes), omo#8577.