[Post-v0.1/P2] Preserve sessions across daemon replacement #32
Labels
No labels
priority/P0
priority/P1
priority/P2
release/v0.1.0
status/blocked
status/planned
type/bug
type/design
type/test-gap
type/tracker
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
natsukium/felis#32
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Follow-up from #12 (P2); not a v0.1.0 blocker.
Why
Because the daemon owns PTY masters, restarting it loses sessions. Solving that requires socket handoff, a drain broker, or per-session workers; weakening daemon ownership would discard the product's distinguishing lifecycle model.
Scope
Acceptance criteria
Triage plan (2026-09-03)
Source-grounded triage against
mainat69076d42, reviewed through seven rounds of an independent reviewer (pisol/luna) until it passed with no findings. The dependency order that supersedes the tracker's is posted on #12. Where a "Review amendments" section below conflicts with an earlier section, the amendment is the decision.Claim check
Accurate, but the issue under-states how much is already decided and built.
crates/felis-daemon/src/pool.rs:86builds each session'sGrid::new(rows, cols)inside the pool, anddocs/explanation/architecture/session-lifecycle.md:1091-1095("Daemon updates") states that a stop closes the PTY masters and every RAM-only state drops.docs/how-to/update-felis.md("Drain your sessions", "Restart the daemon onto it") is the user-facing procedure:pkill felis-daemon.docs/tutorials/first-session.md:132("sessions outlive their windows"),README.md:208,docs/tutorials/drive-without-a-window.md:11. No doc claims daemon-loss survival.e0994073(2026-08-28, "feat!: drop the in-place daemon upgrade from the initial release"). It removedcrates/felis-daemon/src/upgrade/{dump,fd,reexec,restore,strand}.rs(≈1600 lines), thefelis-daemon upgradeverb,ConnMsg::UpgradeDaemon/UpgradeStarting, the quiesce/thaw machinery, the transport fd-inheritance hooks, and felis-protocol's postcard feature.CHANGELOG.md:900-908records the user-facing removal.session-lifecycle.md:1109-1137records drain-as-manual-procedure (with the create-vs-stop race documented, not closed), "old-daemon-serves-until-empty (socket handoff or broker) is explicitly deferred: it needs a cross-platform handoff design v1 does not carry", and the re-exec rejection with its rationale (a private versioned dump schema is a second compatibility-bearing serialization surface; fd-inheritance hygiene; impossible on Windows) and a Revisit if clause: "long-lived sessions become precious enough in practice that losing them on update is a reported pain". The commit message adds the compatibility framing that matters for this issue: the upgrade "can return later as a purely additive feature, since it only ever matters between adjacent versions that both ship it."CLAUDE.md"Workspace policy" still listsfelis-daemon'supgrade::fdfunnels among the audited#[allow(unsafe_code)]sites. That module no longer exists (e5b5c630wrote the sentence on 2026-08-21;e0994073removed the module a week later).grep -rn upgrade crates/felis-daemon/srcfinds onlyWeak::upgradecalls. Worth adocs:/chore:one-liner.Verdict
defer (keep post-v0.1.0, P2) — with one scope correction: the issue's "compare handoff/broker/worker designs" step must start from the removed re-exec implementation at
e0994073^and the recorded rejection, not from a blank page. The principle question is already settled: none of the three designs moves PTY ownership out of the daemon, so principle 3 ("the daemon owns state") is not in play; the open question is upkeep cost versus demand, which is exactly what the Revisit clause names.Deferral does not get more expensive because of the v0.1.0 freeze:
Opsrequest to trigger the upgrade, or nothing at all for a socket handoff). The evolution rules being frozen by #30/#47 allow exactly that (an additive minor may add oneof variants;docs/reference/ipc.md:202-205).session-lifecycle.md:1135-1137). So freezing the wire does not freeze anything the upgrade would later depend on.UpgradeDaemon/UpgradeStartingarms used in a way that blocks re-adding them — it should not, since new variants get new numbers, but thee0994073message notesConnMsgwas repacked dense, so a future re-add is a new tag, never a revival of the old one.What does change after the tag is the observability of demand: the Revisit trigger ("reported pain") can only fire once there are users on a tagged release. That is an argument for deferral, not against.
Approach
Eventual approach, when the Revisit clause fires:
e0994073message: "worked"), needs no second process or proxy in the socket path, and the cross-platform objection ("Windows cannot have it at all") is answered by making it a Unix-only operation that repliesUnsupportedelsewhere — the same shapefelis-pty's ConPTY backend already takes for Unix-only behavior. A broker in front of two daemons would put a proxy in the preface path and is the design the docs already defer for lacking a cross-platform story; per-session workers would multiply the process model for every session, upgrade or not.OpsMsg::UpgradeDaemon { successor_path }→ reply withAccepted/Refused { reason }in the #20/#47/#48 shapes (correlated request, exact per-arm identity). No pre-handshake arm this time: the removed pair was the only cross-generation path and its removal is what let every frame sit behind the generation gate (e0994073). Additive minor.crates/felis-daemon): quiesce each session (stop the PTY drain, flush outboxes), dump structured state with a version tag bound to the build id (#17's build identity), pass the listen fd and PTY masters to the successor via the env-carriedfd:pidmanifest,execve; successor decodes-or-cold-starts. This is the recorded design (session-lifecycle.md:1135-1137), re-audited against the#[allow(unsafe_code)]policy inclippy.toml/CLAUDE.md(theupgrade::fdfunnels come back as an audited site — and the stale CLAUDE.md sentence becomes true again).SIGKILLs the stranded children via the manifest (the removedstrand.rs), so ownership is never split; a successor that fails to exec leaves the old daemon thawing its sessions. Both are integration-tested.felis daemon upgrade [--binary PATH]as a Point verb (--format json→{"v":1, "from": build, "to": build, "sessions": N}), classified indocs/reference/cli.md"Machine output" per #35.session-lifecycle.md"Daemon updates" (flip the rejection into a decision record with the failure modes above),docs/how-to/update-felis.md(new primary path;pkillstays as the fallback and the Windows path),docs/reference/ipc.md(new Ops arm),docs/reference/cli.md,docs/reference/spec.md(a REQ for "sessions survive an in-place upgrade between adjacent versions"),CHANGELOG.md,skills/felis(the upgrade verb), the tutorial claim (first-session.md:132) may widen to "sessions outlive windows and daemon upgrades".UpgradeDaemonto the same binary → reconnect → assert session id, grid, scrollback, image store intact; a corrupted-dump test asserting cold start + explicit child termination; a Windows build asserting theUnsupportedreply. Reuse the harness style ofcrates/felis-daemon/tests/real_app_harness.rs(whiche0994073touched, so it once hosted this).Dependencies
Risk/effort
L. Main risk is the one the removal named: a private serialization of grid/image-store/parser state that every later change to those types must keep adjacent-readable, or knowingly break. Mitigation: decode-or-cold-start (never refuse the upgrade, only downgrade it to "lose sessions, with an explicit log line"), plus a CI test that upgrades HEAD to HEAD.
Labels
Keep
priority/P2, post-v0.1.0. Not a closure dependency for #12. Suggest editing the issue body to citee0994073andsession-lifecycle.md"Daemon updates" as the starting point, and to drop the "gather an observed workflow" bullet in favor of the doc's existing Revisit trigger (reported pain from users on a tagged release), so the issue is actionable the day that trigger fires.Review amendments (round 1)
docs/explanation/non-goals.md"Cross-platform constraints" rejects features gated to one OS. A Unix-onlydaemon upgradereturningUnsupportedon Windows is such a gate. Before implementation, the design must either be uniform (a handoff that does not rely onexec/fd passing, e.g. a socket-level state transfer between old and new daemon processes) or record a decision lifting the non-goal with its rationale. This does not affect v0.1.0 (the issue stays deferred).