Files
hermes-agent/hermes_cli/web_routers
teknium1 f9b8cf7c42 fix(gateway): a failed host attach never exits 0, and SIGTERM clears the record
Reviewer findings on the host rendezvous record + serve attach.

- Attach now PROVES the owner before exiting 0: a bounded TCP connect to the
  recorded endpoint plus a token-authenticated GET /api/host/identity that must
  answer as the recorded pid+role. A supervisor or `hermes update` relaunch
  landing in the old process's graceful-shutdown window (socket closed, atexit
  not yet run) previously exited 0 with NOTHING listening, so the service
  reported success for a dead backend. Anything short of a proven owner falls
  through to the bind.
- Unprovable liveness (no psutil, an unexpected psutil error) is a CANDIDATE,
  not an owner: it goes to the same probe instead of exiting 0, which is what
  turned a record for a long-dead pid into a permanent silent outage.
- An explicitly typed --port/--host the owner cannot serve is a non-zero refusal
  naming the owner, never a silent loopback redirect; and a `hermes dashboard`
  user is never routed to a headless `serve` backend (servesSpa in the identity
  answer).
- SIGTERM — the NORMAL stop (systemd stop, docker stop, the update relaunch) —
  now clears the record and the 0600 token and releases the host lock. atexit
  does not run on it: uvicorn's capture_signals re-raises into the default
  disposition, so a live session token outlived its process indefinitely. The
  handler only prepends cleanup and hands off to the previous handler, leaving
  the shutdown sequence unchanged.
- Host lock claim is tri-state (acquired / held-by-other / could-not-open) and
  logs the OSError: an unwritable lock dir used to be reported as "another
  gateway owns this host", sending operators hunting a process that never
  existed. Its handle cache is keyed by (role, resolved lock path), so a changed
  lock dir can no longer make owns_host_lock() lie.
- Windows: the token is written through the SSH runtime's protected
  owner+SYSTEM DACL writer (os.open(0o600) sets no ACLs there, and os.replace
  fails against an open reader); read_token's docstring no longer claims the
  mode bits prove same-OS-user.
- gateway/status: a RELATIVE $XDG_STATE_HOME is ignored per the XDG spec — it
  made the host lock dir CWD-relative, so two serves started from different
  directories shared no singleton.

Live A/B (real processes): kill -TERM of a fully started `hermes serve` left
host-serve.json + host-serve.token on disk on base, removes both on head (exit
status -15 unchanged). A record with a LIVE pid and a closed port made base exit
0 with no bind; head binds. `serve --port 8899` against an owner on another port
exits 1 naming the owner.
2026-09-21 02:51:58 -07:00
..
…
…
…
…