Cloud: a new chat on a fresh machine with an older cmux-tui updates it and goes on (DEV builds can update machines)

TE
4d ago
+198-6

Leo, 2026-10-31: "whenever i start a chat it should be able to send me to cloud autommatically keep launching/downloading new dev builds and such".

The bug

A fleet capture of that flow (a DEV build signed in to the Cloud test account on staging, a local Git project, Start New Chats In: Cloud, the first message sent through the composer) made a fresh cloud-chats machine, then failed the send at the copy step with:

cloud-chats runs an older cmux that can't take this chat yet. Update the machine to move it.

There were two causes:

  1. The machine's image carries an older cmux-tui than the app. The app updates every Cloud machine to its own build on the machine's first handshake (CloudMachineUpdates, on by default), and an agent tab start that an older daemon refuses already updates the machine and tries again. The start's copy runs before that, and nothing retried it.
  2. A DEV app could never update a machine anyway. cmux link machine update pins the machine's build from the cmux-tui-ssh/manifest.json that release builds ship, and a DEV build ships none, so the update answered machine_update_unavailable ("dev builds cannot update machines").

Change

  • Rust, cmux-remote pinned_linux_payload. A build with no bundled manifest takes the pin from its own commit's published manifest: files.cmux.com/cmux-tui/<BUILD_IDENTITY>/manifest.json, the same immutable release the download comes from. The app's SSH installer already does this for DEV builds (RemoteInstaller.plan). The manifest must name that exact commit and carry a well-formed digest for the platform, and the download is still checked against that digest. A dirty or unpublished build gets None, as before.
  • App, cloud.startChat's copy. When the machine's daemon refuses the copy with machine_outdated (about the machine, not this Mac's side), the app calls CloudMachineUpdates.updateNow and copies once more on the reconnected socket. updateNow puts the machine at the front of the shared update policy's queue and joins an update of it that is already running. It installs the app's own build with --to-bundled --if-older, so it never moves a machine back, and terminals and chats keep running. With the update toggles off it does nothing, and the start fails as before. A Cloud failure thrown inside the copy keeps its own words instead of becoming "sync failed".

Together these keep every new machine on the app's cmux-tui for every later build, with no image rebake needed for it.

Tests

  • cmux-remote published_pin_tests (on the fleet's Linux pool: cargo test -p cmux-remote --lib published_pin_tests, clippy -D warnings, cargo fmt --check):
    • a published manifest pins the musl build's URL, digest and cache path;
    • a manifest that names another commit, has a short digest, is not JSON, or has no digest for the platform is an error;
    • only a plain 40-hex commit is looked up.
  • CloudStartChatMachineTests.onlyTheMachinesOutdatedRefusalUpdatesItBeforeCopyingAgain.
    Agent session: agent session

🤖 Generated with Claude Code