feat: hot-swap upgrade — replace the binary while every tab stays live - #23
feat: hot-swap upgrade — replace the binary while every tab stays live#23williamdes wants to merge 2 commits into
Conversation
755f5f7 to
82e510e
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #23 +/- ##
==========================================
+ Coverage 52.47% 59.08% +6.60%
==========================================
Files 92 95 +3
Lines 28268 29440 +1172
==========================================
+ Hits 14835 17396 +2561
+ Misses 13433 12044 -1389
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
82e510e to
da6699f
Compare
893e424 to
ee38710
Compare
6ebd05c to
bf12052
Compare
9749627 to
323e2f3
Compare
Add src/hotswap.rs: on 'tab-atelier upgrade' / POST /upgrade the running process re-execs the binary installed at its own path, handing each tab's PTY master fd (CLOEXEC cleared) plus a pid-validated JSON manifest across the exec. Because exec keeps the pid, tab shells remain our children — process groups, controlling TTYs, cgroups, and nftables rules are untouched, so agents and builds inside the tabs never notice. The new image adopts the fds at boot (AdoptedPty mirrors alacritty's Unix Pty: same poller tokens, SIGCHLD pipe, waitpid exit detection) instead of forking shells; grid contents restore through the existing saved-output replay and the carried raw ring bytes re-seed viewer scrollback. PTY readers freeze during the handoff so unread bytes wait in the kernel and are parsed by the new binary — nothing is lost. Adopted tabs skip exactly the work that assumes a fresh shell: agent auto-resume (would double-launch the still-running claude), the GUI net-off bubblewrap respawn (still jailed), the headless nftables teardown/re-apply (would blip enforcement; only the gating DNS resolver is respawned), the cgroup stale reap, and the agent reaper's provenance record (removed at swap so the new boot can't SIGKILL the inherited fleet). A shell that dies mid-swap falls back to a normal fresh fork; a failed exec rolls back fully and the old binary keeps running. Trigger surface: POST /upgrade (master token, 409 when no binary at the re-exec path, 501 on Windows), 'upgrade' subcommand on both binaries, docs/hot-swap.md, openapi.yaml entry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LTkVxFVGsaywyG4iJyTyvc
323e2f3 to
145b078
Compare
|
CI note: The three failing tests were added on main by 8128a04 (“test(cli): drive the CLI verbs against a real in-process API server”), and main’s own push run on that commit fails identically (run 33722747048):
All three pass under Generated by Claude Code |
The three tests added with the in-process API harness assumed the headless edition on a host with bubblewrap — exactly what CI's GUI lib run is not, so main went red: - net-off is refused (412) without bwrap on the daemon host; assert the refusal there and the queued jail only when bwrap exists. - net-allow answers 501 on the GUI (nftables enforcement needs CAP_NET_ADMIN the desktop build does not have): assert the verb FAILS there, and keep the queue assertions headless-only. - ssh-agent is the same 501-on-GUI story; the queue checks move under cfg(not(feature = "gui")) and the GUI asserts the refusal. Verified green in all four combinations: GUI/headless lib tests, each with and without bwrap on PATH. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LTkVxFVGsaywyG4iJyTyvc
|
Update: opened #50 with the fix for the three edition/host-gated CLI tests, and cherry-picked the same commit onto this branch (9e26330) so CI here goes green without waiting on the merge. Once #50 (or an equivalent fix) lands on main, the next rebase will drop the duplicate commit automatically. Generated by Claude Code |
Add src/hotswap.rs: on 'tab-atelier upgrade' / POST /upgrade the running
process re-execs the binary installed at its own path, handing each
tab's PTY master fd (CLOEXEC cleared) plus a pid-validated JSON manifest
across the exec. Because exec keeps the pid, tab shells remain our
children — process groups, controlling TTYs, cgroups, and nftables
rules are untouched, so agents and builds inside the tabs never notice.
The new image adopts the fds at boot (AdoptedPty mirrors alacritty's
Unix Pty: same poller tokens, SIGCHLD pipe, waitpid exit detection)
instead of forking shells; grid contents restore through the existing
saved-output replay and the carried raw ring bytes re-seed viewer
scrollback. PTY readers freeze during the handoff so unread bytes wait
in the kernel and are parsed by the new binary — nothing is lost.
Adopted tabs skip exactly the work that assumes a fresh shell: agent
auto-resume (would double-launch the still-running claude), the GUI
net-off bubblewrap respawn (still jailed), the headless nftables
teardown/re-apply (would blip enforcement; only the gating DNS resolver
is respawned), the cgroup stale reap, and the agent reaper's provenance
record (removed at swap so the new boot can't SIGKILL the inherited
fleet). A shell that dies mid-swap falls back to a normal fresh fork;
a failed exec rolls back fully and the old binary keeps running.
Trigger surface: POST /upgrade (master token, 409 when no binary at the
re-exec path, 501 on Windows), 'upgrade' subcommand on both binaries,
docs/hot-swap.md, openapi.yaml entry.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01LTkVxFVGsaywyG4iJyTyvc