Come back from a restart with the sessions that were running - #52
Conversation
The server owns every PTY, so a Space sleep, a reboot, or a factory reset ends all of them at once. Files and conversations survive on the bucket, but coming back means clicking each agent again — and a shell that was running something is simply gone, with nothing to show it ever mattered. So keep a runstate snapshot on the bucket (every 30s): which sessions are alive, and which have a process actually running in them. On the next boot, start again the ones that were still yours — you prompted them inside the configured window, or something was still running in them. The second rule is the only signal a plain shell leaves, since it has no transcript to read a prompt out of. Their scrollback returns from the existing terminal history checkpoint, so a reopened pane reads as you left it. "Is anything running in there" is one procfs read of the pane root's children, not a process walk: the shell or CLI is the pane root, and anything it started — a foreground command, a `make &`, an agent's tool call — is a child of it. Settings → General → Restart sessions after a reboot: 1 / 3 / 7 days, or off. Also persists the sidebar's folded groups per browser, alongside the theme and zoom. Deliberate limits, all logged rather than silent: revivals are staggered 4s apart and capped at 12 (most recent first; the rest are named in the logs), a snapshot older than 30 days starts nothing, and there is no SIGTERM write — at shutdown the PTYs may already be going down, so a final write is as likely to record an empty Space as a true one, and a 30s-stale truth beats a fresh lie. Never revived: passive panels, remote agents (they run on their own machine), sessions you stopped before the reboot, and anything already up. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… record Three problems a review found, two of which made the feature quietly wrong. The pane root is not the thing you're talking to. `codex` is a `#!/usr/bin/env node` launcher that keeps the native binary as a permanent child, and every `cont || exec run` launch shape leaves bash as the pane root with the CLI as its child — verified on live processes here: both idle codex panes report a child, forever, while idle claude panes report none. So "work in flight" was permanently true for whole CLI families, and the window meant nothing for them: they would revive on every restart at any setting. Scope the signal to shells, which is where the rule came from — an agent has a transcript, so recency can already see it; a shell has nothing else. Re-checked when reading a snapshot too, so an older file can't revive an agent on a flag that was never valid for it. `settleAt` was set only on the success path, so every early return let the watch overwrite the previous snapshot 30s later — including the two failures you would most want to survive: no terminal engine, and a Space still locked because the visibility check hadn't landed within the boot's 9s wait. Both revive nothing AND destroyed the record that would have let the next boot do better. Hold the first write in init(), so it holds on every path. `lastInputAt` counted the emulator answering the TUI. The client sends all of onData, and xterm replies to device-attribute and cursor-position queries the moment a pane attaches — so "you sent this session something" really meant "a pane was open on it". Filter the reply shapes; a key never looks like one. Also: mark a snapshot written only after the write succeeds, so a FUSE write that threw is retried rather than remembered. Tests: the passive-panel case was vacuous (the fixture had no clock, so the guard was never what excluded it), plus the window boundary, the stale work flag, and the reply filter. 28 assertions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Adversarial review pass done; pushed fixes in c258697. Two of the findings were real bugs that made the feature quietly wrong. The work signal was permanently true for whole CLI families. Every early return erased the record.
Plus: a snapshot is marked written only after the write succeeds, so a FUSE write that threw is retried instead of remembered. Tests — the reviewer mutation-tested the suite and caught one vacuous case: the passive-panel fixture had no clock, so the window excluded it whether or not the guard existed. It has one now. Added the window boundary, the stale work flag, and the reply filter: 28 assertions, and Known and not addressed here, worth a follow-up rather than scope creep:
|
A Space sleep, a reboot, or a factory reset ends every PTY at once — the server owns them all. Files and conversations survive on the bucket, but coming back means clicking each agent again, and a shell that was running something is simply gone with nothing to show it ever mattered.
This keeps a runstate snapshot on the bucket (refreshed every 30s): which sessions are alive, and which have a process actually running in them. On the next boot it starts again the ones that were still yours:
make &left in the background, an agent mid-tool-call.The second rule is the only signal a plain shell leaves: it has no transcript to read a prompt out of. Scrollback comes back from the existing terminal-history checkpoint, so a reopened pane reads as you left it. Agents resume their own pinned conversation and wait — nothing is re-sent.
Settings → General → Restart sessions after a reboot: 1 day / 3 days / 7 days / off (default 3 days).
Also in here, since it is the same complaint: the sidebar's folded groups persist across reloads, per browser, alongside the theme and zoom.
How "is anything running in there" works
One procfs read per live session, no process walk and no subprocess: the pane root is the shell or the CLI itself (
paneRootPid), and anything it started is a direct child, which Linux hands us at/proc/<pid>/task/<pid>/children. Needs no new runner exports.Deliberate limits — all logged, none silent
lastInputAtis throttled to one write per session per minute — it is recorded per keystroke and each write lands on the FUSE bucket.Never revived: passive panels, remote agents (they run on their own machine), sessions you stopped before the reboot, and anything already up.
Verification
server/test/revive.test.mjs— 13 cases over the selection rule (each window, work-only, and every never-revive case), wired intonpm test.npm testpasses on this branch, with the engine installed: all checks passed.sleep 300 &, one idle, one typed into) → snapshot →stopAll()→ revive. The snapshot recordedwork: truefor exactly the busy one, and exactly the busy and typed-in shells came back. With the setting off, nothing started.tsc --noEmitclean.Note for a follow-up
Settings still tells you a factory reboot is safe because "Sessions survive on the bucket", and the README said something similar. That stopped being true when tmux left; the README paragraph is updated here, but the Settings copy is left alone to keep this diff to one subject.