mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-22 00:02:23 +00:00
Picking up a new build meant stopping the daemon, and stopping the daemon means every pane dies: the pty master is a descriptor this process holds, so when the process goes the slave side raises SIGHUP and takes the shell, the agent and the half-finished command with it. That is why the update path leaves the old daemon serving and Settings has to offer the restart as a thing you schedule for a quiet moment. `execve` does not have that problem. It replaces the image and keeps the process: same pid, same children, same descriptors, same file locks. The daemon now rewrites itself that way on `ClientMsg::Handoff` — it writes what it knows about each pane into a blob, clears FD_CLOEXEC on the pty masters, the blob and the singleton lock, and execs the new binary, which picks the panes back up on the other side. - **the seat travels on the command line, not in the blob.** The lock is still held by this process, so the new image must adopt the descriptor rather than ask for the lock again — asking would be refused by its own lock and it would stand down in favour of itself. A daemon that loses its panes is a bad afternoon; a daemon that exits leaves the machine with nothing serving, so that one fact has to survive an unreadable blob. - **the blob is unlinked before it is written.** It holds every pane's ring, which is the output `scrollback` makes people opt into storing; a handoff must not be a back door for writing it to disk. - **the exec is the last step.** Everything is staged first, so any failure before it costs a log line and the daemon carries on serving — which is what lets callers treat a failed handoff as "fall back to a restart" without having lost anything on the way. Native SSH panes cannot cross — their session is cipher state in memory, not a descriptor — so they are hung up first and the far end sees a clean close. Windows has neither execve nor a transferable ConPTY handle, so it keeps the stop/start path; the dialogs there still promise what they always did, and the new copy is shown only where it is true. Also retries flock on EINTR: a signal landing mid-call said nothing about the lock, but was reported as "could not be evaluated", which starts a second daemon beside the first — the split machine singleton exists to prevent. The end-to-end test sets a variable in the shell, hands over, and reads it back. Nothing but the original process can answer that, and the daemon's instance id changing while its pid does not is what says an exec really happened.