Skip to content

fix(daemon): self-evict when socket is taken over; safe unlink-on-close#13

Merged
couragehong merged 1 commit into
CryptoLabInc:mainfrom
couragehong:fix/runed-orphan-self-heal
Jun 9, 2026
Merged

fix(daemon): self-evict when socket is taken over; safe unlink-on-close#13
couragehong merged 1 commit into
CryptoLabInc:mainfrom
couragehong:fix/runed-orphan-self-heal

Conversation

@couragehong

Copy link
Copy Markdown
Contributor

runed is meant to be a singleton on $RUNED_HOME/embedding.sock, but when the home dir is recreated out from under a running daemon (e.g. a dev rm -rf ~/.runed + reinstall) the socket path and lock files get new inodes, so the single-instance guards fail open and a second daemon spawns. The old daemon keeps running on a path that no longer points at it, and nothing reaps it -- orphan daemons accumulate. Separately, Go's UnixListener unlink-on-close lets a dying daemon delete a healthy peer's socket file that happens to occupy the same path.

  • ipc.Listen disables net's unconditional unlink-on-close and records the bound socket's (dev,ino); Close removes the file only if the path still refers to our own socket, and StillOwned exposes the same check. A cross-platform Listener interface carries StillOwned to callers.
  • runed runs a self-eviction watchdog: if its socket is unlinked or rebound by another daemon, it triggers the normal graceful shutdown and cancels the daemon context, exiting 0 so the supervisor stays down rather than restarting into the stolen path. Boot-failure paths return nil when evicted so an eviction during self-bootstrap also exits 0.

Adds regression tests: StillOwned transitions, and that an evicted listener's Close does not delete a socket another listener rebound.

runed is meant to be a singleton on $RUNED_HOME/embedding.sock, but when
the home dir is recreated out from under a running daemon (e.g. a dev
`rm -rf ~/.runed` + reinstall) the socket path and lock files get new
inodes, so the single-instance guards fail open and a second daemon
spawns. The old daemon keeps running on a path that no longer points at
it, and nothing reaps it -- orphan daemons accumulate. Separately, Go's
UnixListener unlink-on-close lets a dying daemon delete a healthy peer's
socket file that happens to occupy the same path.

- ipc.Listen disables net's unconditional unlink-on-close and records the
  bound socket's (dev,ino); Close removes the file only if the path still
  refers to our own socket, and StillOwned exposes the same check. A
  cross-platform Listener interface carries StillOwned to callers.
- runed runs a self-eviction watchdog: if its socket is unlinked or
  rebound by another daemon, it triggers the normal graceful shutdown and
  cancels the daemon context, exiting 0 so the supervisor stays down
  rather than restarting into the stolen path. Boot-failure paths return
  nil when evicted so an eviction during self-bootstrap also exits 0.

Adds regression tests: StillOwned transitions, and that an evicted
listener's Close does not delete a socket another listener rebound.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@couragehong couragehong merged commit 5428f41 into CryptoLabInc:main Jun 9, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants