mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-21 16:02:20 +00:00
A daemon can survive quit-and-stop with its endpoint unlinked and its pidfile gone while still holding the singleton seat (#667). Every later launch then spawns a daemon that stands down against the lock and times out red, and nothing on the machine can recover: stop answers "not running", ensure_running reaps only through the pidfile, and flock cannot say who the holder is. Two roads led there, and both are closed: - The reap identified a daemon by proc_pidpath alone, which fails outright for a live process whose binary was deleted — every nightly update replacing the installation. The identity check now falls back to the kernel's comm name (proc_name on macOS, /proc/pid/comm on Linux, both recorded at exec and immune to deletion), strips Linux's " (deleted)" marker, and — decisively — no longer deletes the pidfile of a live process it cannot identify: the record was the only handle left on the survivor. - When the pidfile is gone entirely, the pid the claimant now writes into daemon.lock at claim time is the handle of last resort. The lock file is never deleted and holding the flock is the definition of being the server, so while the seat is held its content names the holder; stop() and the reap fall back to it, and a confirmed reap clears the record (only under a momentarily-free seat) so a stale number cannot outlive its process. Unix-only: the Windows seat is share_mode(0), unreadable while held. Every road back now clears a stranded seat, not just the GUI's: ensure_running's stale cleanup is factored into spawn::reap_stranded, tty7 server start runs it too, and tty7 server stop no longer takes "nobody answered" for "nothing to stop" when the seat is still held. A short grace keeps the reap away from a daemon that is merely mid-handoff or mid-startup — where health is an answered handshake, never a bare connect: a wedged daemon's listener still completes connections out of the kernel's backlog. The startup-timeout errors name the seat-holding pid, with the kill advice identity-gated so a stale record never tells anyone to kill an innocent process. Two liveness corrections round it out: a zombie now reads as dead — it answers kill(pid, 0) like the living but holds no lock and no image, and no signal can end it, so counting it alive spent both reap timeouts on a corpse (the GUI never waits on the daemons it spawns, so crashed daemons are zombies as a rule) — and stop() only pays the process-exit wait for a shutdown it actually delivered, instead of watching an unreached survivor not move for five seconds. The guard tests were each verified to fail against the behavior they pin (fallbacks, the handshake criterion, the grace, and the wait gate removed by mutation) before being trusted green; the zombie probe semantics (proc_pidinfo failing for a zombie that still answers signal 0) were measured, not assumed.