cmux NEXT Cloud: Machine Status applies its record through the list's pause/resume transition
Leo, on this area (2026-10-29): "a whole lot of this cloud stuff ought to get better..."
What was wrong
Machine Status (palette.cloud.status) reads the machine's record by itself and stored it with session.machine = record. That skipped the live/not-live handling a list refresh runs (CloudService.reconcile): when a live machine stops being live, the session suspends, and when it turns live, the session connects. So:
- If Machine Status saw that the idle auto-pause had parked the machine, the next list refresh found "was not live" and never suspended. The link stayed up or failed, and the header never read "Paused (idle)".
- If the machine had been resumed elsewhere,
connect()never ran, and the session stayed disconnected.
The fix
CloudService.apply(_:to:) now holds the per-machine body of reconcile: it stores the record, then suspends or connects. reconcile and Machine Status both call it.
Reads can land out of order (two Machine Status reads, or one racing a list refresh). So every read, whether a list or one record, takes a ticket from CloudService.beginRecordRead() before it is sent. A machine applies a record only from a read newer than the one whose record it already holds. An older answer that lands later changes nothing: no record overwrite, no suspend or connect. A list that first shows a machine records its ticket for that machine when it creates the session. The same check guards removal: a list that omits a machine removes it only if no read asked after that list applied its record. The check is per machine, so a Machine Status read never discards a whole list (its creates, deletes or first load). No other behavior changes.
The test
In CloudIdlePauseTests ("One record (Machine Status)"):
oneRecordThatTurnedPausedSuspendsTheSession: a running legacy-link session that is connecting gets a paused (idle) record. After that,linkPhaseis.idle, the daemon is not starting, andstage == .paused(idle: true).oneRecordThatTurnedLiveConnectsTheSession: a paused session gets a running record, and its daemon starts.anOlderRecordLandingAfterANewerOneChangesNothing: a newer paused record suspends the session. Then an older running record (one record, and an older list throughreconcile) lands, and the session stays paused, unconnected and "Paused (idle)". A read asked afterwards applies as before.aMachineANewerListAddedKeepsThatListsTicket: a newer list shows a machine first. An older Machine Status record does not overwrite it, and an older list without it does not remove it. A test process has no cmux-tui for a legacy link, so the test stands in the session that list's create would make. Red at its test commit, green at head.anOlderListWithoutTheMachineDoesNotRemoveANewerRecord: a list asked before a Machine Status read lands without the machine, and the machine stays. A list asked later removes it. Red at its test commit (a behavior failure, not a compile one), green at head.
Red at the test commits: they do not compile, because the method each test calls does not exist there yet. At 57550a7cf06 that is CloudService.apply(_:to:), and at 9a1f887aa6b it is the read ticket. Green at head. CI will show.
Observed / Not run
- Observed:
python3 scripts/cmux-next/prepush-gate.pyreports no new violations. It warns (does not refuse) thatapp-sizegrows by 26 App lines. - Not run: this host builds no Swift. The suite runs in CI, and I have not seen it run. I read the code to confirm the defect and the handler's main-actor context. I did not run it in the app.
Changelog
- Fixed: Machine Status on a Cloud machine the idle auto-pause parked now shows it as Paused (idle) and stops its link, and a machine resumed elsewhere connects again.
risk:normal
No capture: state handling, covered by the regression test
Agent session: agent session
š¤ Generated with Claude Code