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+198+012345678901234567890123456789-6-6-0123456789
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:
- 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. - A DEV app could never update a machine anyway.
cmux link machine updatepins the machine's build from thecmux-tui-ssh/manifest.jsonthat release builds ship, and a DEV build ships none, so the update answeredmachine_update_unavailable("dev builds cannot update machines").
Change
- Rust,
cmux-remotepinned_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 getsNone, as before. - App,
cloud.startChat's copy. When the machine's daemon refuses the copy withmachine_outdated(about the machine, not this Mac's side), the app callsCloudMachineUpdates.updateNowand copies once more on the reconnected socket.updateNowputs 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-remotepublished_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