Skip to content

Stop a restart from inventing work a session isn't doing - #1171

Merged
edwin-zvs merged 1 commit into
mainfrom
resume-keeps-idle-state
Aug 2, 2026
Merged

Stop a restart from inventing work a session isn't doing#1171
edwin-zvs merged 1 commit into
mainfrom
resume-keeps-idle-state

Conversation

@edwin-zvs

Copy link
Copy Markdown
Contributor

A headless Slack service session shows an animating running icon after every daemon restart, with no turn in flight. Clicking into it stops the animation and it drops to idle.

What's happening

respawn sets SessionState::Running on both of its branches — including the one where the adapter outlived the daemon and the daemon merely reattached to it. Nothing about that adapter changed across the restart. It is blocked in the same inbox.recv() it was blocked in before, and an adapter waiting for input has no reason to announce anything: its next status arrives when a turn ends, which is to say never, until someone starts one.

That makes the optimistic Running unfalsifiable:

  1. The only corrector for harnesses that don't self-report is the PTY quiescence sweep, which needs last_pty_at_ms to measure silence against.
  2. A headless session's child emits no PTY output between turns, so that field stays None and the sweep never fires.
  3. The TUI animates a headless session on state == Running alone.
  4. Opening the session hydrates it and sends a pty_resize (with the +1 column bump), the child repaints, PTY bytes finally arrive — and now quiescence flips it to AwaitingInput. Hence "clicking it fixes it."

Meanwhile set_state_tracked had opened a busy span on that false Running, so the session banked its idle hours as compute. The session in the report was sitting on ~6.7M ms of it.

The fix

A reattached session keeps the state it was persisted with — AwaitingInput stays AwaitingInput. Every other state still resumes as Running, which is what a live adapter means for them: Running was mid-turn and still is, Pending never reported anything, and Errored must stop looking terminal now that its adapter answered.

The spawn path is deliberately untouched. There a replacement child really is booting, so the placeholder is honest — and marking it AwaitingInput early would re-open the spec-0149 hazard of input pasted into a still-drawing harness.

Tests

  • reattach_leaves_an_idle_session_idle — unit-covers the state mapping.
  • restart_keeps_an_idle_session_idle — e2e: creates a session, waits for it to park, restarts the daemon, then samples state every 100ms for 3s and requires idle at every sample. Verified it fails on the old behavior (left: Running, right: AwaitingInput) and passes with the fix.
  • Full cargo test green. (service_supervisor::…hand_a_port_to_another… flaked once under concurrent load, passes 3/3 on rerun; it's a port-binding test, unrelated to lifecycle.)

specs/0180-a-restart-does-not-invent-work.md records the rule: resume must treat "the adapter is alive" and "the session is working" as different facts.

A daemon restart that reattaches to an adapter which outlived it was
rewriting the session's state to Running. Nothing about that adapter
changed across the restart: a session parked at its harness's prompt is
still parked there, blocked on input, with no reason to emit another
status.

For a PTY harness the idle sweep eventually walks it back, because
silence is measurable against output. A headless session emits none
between turns, so the false Running was unfalsifiable — it survived
until a client happened to open the session and the hydration resize
forced the child to repaint. Until then the fleet painted a working
spinner over an idle session, and the busy span banked hours of compute
the session never spent.

So an idle session now comes back idle. Every other state still resumes
as Running, which is what a live adapter means for them, and the spawn
path — where a replacement child really is booting — is untouched.
@edwin-zvs
edwin-zvs merged commit fc1497a into main Aug 2, 2026
1 check passed
@edwin-zvs
edwin-zvs deleted the resume-keeps-idle-state branch August 2, 2026 17:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant