Repository navigation
Closed
Description
Parent
Part of #154 (remote agent sessions). Phase 3. Design doc docs/designs/remote-workrooms.md, Distribution Plan, "Resume policy versus version lockstep", DECIDED (2026-09-22, owner): hand off.
What to build
A newer agent never kills an older one's sessions, and never leaves the old one running indefinitely either. The outgoing agent hands its sessions to its replacement. The local Mac hands off on app update too (decided 2026-09-22), shipped on Nightly first, because it proves the mechanism on the host that is easiest to debug before the remote bootstrap depends on it.
- Replace the program in place:
execve, not a second process.Pty::waitis awaitpidon the child, which works only for the child's parent. Passing pty masters to a separate process (SCM_RIGHTS) would keep the shells alive but orphan them, losing every exit code. The old agentexecves the new binary, so its pid stays the same and it stays every shell's parent. The pty masters and the listening socket survive once close-on-exec is cleared, so the socket is never unbound. The session table (ids, pids, fds, sizes, size owner) crosses in an inherited pipe or memfd. - The screen crosses as VT bytes, never as a snapshot. Two agent revisions share no snapshot format. The outgoing agent calls
replay()on each live terminal, which is the only place the parser continuation can be read, and the incoming agent feeds those bytes to a fresh terminal of the same size. - Check first, and refuse rather than kill. Before it
execves, the old agent runs the new binary once to confirm it starts on this host AND can restore sessions (so a downgrade to an app that predates hand-off keeps the newer agent running). If the check fails, or the old agent predates the hand-off request, it keeps running and the app talks to it over the versioned envelope. A failedexecvereturns to the old program with nothing lost. - The residual risk is a new binary that passes the check and then fails while it restores. Its test is to crash the restore on purpose.
- What it ends. Today an older local agent lives until 30 s after its last terminal closes (
DEFAULT_IDLE_TIMEOUT), which can be days, and GitHub status, port forwarding and the busy/idle verdict have no native fallback while it does.
Acceptance criteria
- On app update, the running local agent hands off to the bundled one: the same shells survive with the same pids, their exit codes still reach clients (
the_shells_exit_code_becomes_the_clients), and each reattaching pane is repainted. - The listening socket is never unbound during the hand-off.
- The pre-check refuses a binary that cannot start or cannot restore, and the old agent keeps running. An agent that predates the hand-off request is not replaced.
- A test crashes the restore on purpose and records exactly what is lost.
- Hand-off ships on Nightly first. The pre-check, the refuse fallback and the crashed-restore test gate the stable channel.
Blocked by
None. This is local-only and can run in parallel with #227–#229.
Filed by Claude Code at the owner's request, 2026-09-24.
Metadata
Metadata
Assignees
Labels
No labels
Activity
docs(macapp): document the agent hand-off build fix and pointer
feat(agent): hand a running agent to a new binary, keeping every session
fix(agent): address #230 pre-landing review findings
fix(agent): address #230 round-2 review findings
fix(agent): bound a hand-off's freeze and reap exiting shells under T…
fix(agent): take the store lock within a hand-off's freeze deadline
fix(macapp): rename a rebuilt wr-agent into place instead of writing …
docs(macapp): document the agent hand-off build fix and pointer
docs(remote-workrooms): mark #230 merged
feat(macapp): push the Linux agent to a remote host and hand off to it
docs(macapp): document #232 screen restoration for reboot recovery
docs(macapp): document #232 screen restoration for reboot recovery
feat(macapp): hand the local agent off in every build