cloud new-machine: --persistent, --size, --name and a bootstrap script in one call
Leo (2026-10-25, via claude-sr2): "One command for a persistent machine: cmux cloud new-machine --persistent --size ..., with built-in bootstrap, no idle pause, and automatic acpmux and fleet-registry enrollment. Then use it to create the lane VMs in machines/lane-vms.md, and make lane-place able to create them on demand."
cmux cloud new-machine --persistent --size medium --name lane-vm-1 \
--bootstrap lane --bootstrap-script tools/lane-vm-bootstrap
This makes the machine in one call and answers JSON.
- Size and name go with the create.
--size small|medium|large|xlargesendsmemoryMb(4/8/16/24 GiB), and the server's size ladder picks the vCPUs (2/4/8/12); the answer reports both.mediumis the lane VM's 4 vCPU / 8 GB.--namemust be a plain word: 1-64 letters, digits,.,_or-. That makes it a valid host name and shell word in the commands the answer prints.
--persistentturns the idle pause off at create (idlePauseMinutes: "off"; the server half is #25216).- If the server didn't store it (an older deploy), the app PUTs
/idle-pauseright after. - If that fails too,
persistentreads false andenrollment.pendingnames thecloud set-idle-pausecommand. persistentreports what the server stored;persistent_requestedreports what was asked.
- If the server didn't store it (an older deploy), the app PUTs
--bootstrap-script FILE(--bootstrap lanealso setsLANE_VM_NAME):- The script must be a regular file of at most 1 MB. It is read on the app's files lane before anything is created, so an unreadable script costs no machine.
- It is uploaded with the Cloud file API to
/home/cmux/.cmux/bootstrap/and started by one short exec as a transient systemd unit (cmux-machine-bootstrap). If systemd orsystemd-runfails, it falls back tosetsid. A long bootstrap therefore never hits exec's 25 s limit. - Every command names the image's work user
cmux, so the script always runs ascmuxwith its home, whether the exec runs as root or as that user. - It logs to
bootstrap.logand writesstatus.json(running|done|failed, exit code, script sha256). - A rerun of the same script while it runs (flock) or after it finished starts nothing.
- The CLI resolves a relative path against its working directory, and a quoted
~/is expanded.
- The answer:
machine_id,target,name,size,cpu,memory_mb,persistent,idle_pause_minutes,address,bootstrap {status, log, status_file, check},enrollment {done, pending, enroll_command, registry_command}. After 30 s it answerspending, and the create and bootstrap go on. - A slow create still finishes. Each request has one idempotency key.
- A
503 vm_operation_in_progressor409 vm_create_in_progressis sent again with that key, up to 70 times 15 s apart. That covers the server's 16-minute provisioning budget. - The first send's team is pinned for the retries, the bootstrap and the idle-pause write, so a team switch in the app can't turn the key into a second machine in another team.
- The bootstrap starts inside the creation's own create step, so the Retry button starts it too. A Retry skips the local plan gate to reach the server's lookup of its key.
- A
- A text flag with no value (e.g.
--bootstrap-scriptlast on the line) is refused, not ignored. - The app-server Cloud backend (
CMUX_NEXT_CLOUD_LINK=app) refuses every option for now: its create takes none, and its machines have no file or exec route for a bootstrap. - A bare
cloud new-machine, the palette and the menu take the old path unchanged.
Why upload over the file API instead of baking the bootstrap into the image (cloud-init, a first-boot unit): the lane bootstrap lives in leo-workspace and changes weekly, and the image bake is a separate, manual pipeline. The upload rides the signed-in person's authenticated link, holds no secret and needs no image change.
Enrollment (ssh route and acpmux peer) is #25263 (cmux cloud enroll-machine). The leo-workspace registry entries come from tools/lane-vm-enroll.
Supersedes nothing, and shares no files with #21028, #7560 or #18670.
Changelog
- Added:
cmux cloud new-machine --persistent --size --name --bootstrap-scriptmakes a sized, never-idle-paused Cloud machine and starts its setup script in one command.
Observed / Not run
- Ran here:
prepush-gate.py: no new violations.- The localization check: no new findings in the touched catalogs.
- A local run of the start command and runner (the
setsidpath, as usercmux): it printedstarted,runningandskipped, and wrotedoneandfailedstatus as expected.
- Review: Codex
gpt-6-astraat xhigh through SR2, 10 rounds, SHIP at 36f2dc81ef03d6d7b5938c63fb3aaf0b1d13f017. Two read-only compile passes found three compile errors, all fixed:Kindwas missingEquatable, aString+Substringconcatenation, and a missingpublic. Round 8 noted that a session added after an active-team switch attaches with the new team's header. Round 9 confirmed that is base behavior, since every Cloud session uses the app's one API client, and not caused by this change. - Not run here (no builds on this host): the Swift suite
CloudNewMachineTestsand the Rust testnew_machine_bootstrap_script_is_made_absolute. This PR's CI runs them. No live create either, since there is no signed-in app here; p0-machines runs the lane VM commands on a Mac.
No capture: CLI-only (no UI changes; the palette row and menu item are unchanged).
Timers/loops: the same-key create retry is bounded at 70 tries, 15 s apart, and ends with the create. There is nothing else in the app. On the machine, the bootstrap runner ends when the script does, and its unit is transient (--collect).
Risk: normal.
Agent session: acpmux 01a1756f-4a01-79f0-a405-561d20fd162c on gcp-agent-lane-1 (pm-newmachine, helper of p0-machines)
š¤ Generated with Claude Code