Cloud: fork from a base machine, with working and optimistic forks

Merged
AU
10d ago
+1065-72

New Machine can now start from an existing machine (Base selector), and Fork works from every entrypoint: the sidebar row menu, the menu-bar Cloud menu, Cmd-Shift-P, and New Machine → Base.

Fork was broken. A fork is a memory snapshot of a running machine. On clone, the guest supervisor (cmux-devbox-boot) deletes only sessions/<s>/auth, leaving lifecycle-fence.json, and cmux-tui refuses to start on a fence without auth ("previous remote daemon authorization state requires explicit migration"). The clone's daemon crash-looped and port 1337 never listened, so the machine existed but never attached. FreestyleProvider.create now runs a readiness step for forks and checkpoint restores (web/services/vms/images/remoteState.ts): it waits until /etc/cmux/daemon-instance-id names this machine's metadata instance id (so the source's resumed daemon can't satisfy the check), removes only sessions with a fence and no auth, then waits for this machine's listener. A live daemon always holds auth, so the repair never touches a working session. Verified on a stranded fork: the daemon came up within one supervisor tick and vm workspace new attached.

The durable fix (supervisor drops the whole remote dir on clone, as the bake does) changes image sources, which the manifest provenance gate ties to a 12-image rebake, so it is left for the next bake. Until then the backend repair covers every existing image.

Fork is now optimistic. All Fork entrypoints call NewMachineSheetPresenter.startFork, which reserves a "Fork of " pending row ("Forking…") and a loading workspace through the same MachineCreateCoordinator New Machine uses, before any process or network work. cmux vm fork prints the OK machine= receipt so the row hands off to the real machine, and uses the same store-based idempotency key as vm new (scoped by source and reserved workspace) so Retry after an unknown outcome joins the first attempt instead of billing a second copy.

Speed. Freestyle has no native fork; its documented equivalent is vm.snapshot() then vms.create({ snapshotId }), which we use. Measured on a tagged dev backend: the fork route's application code took 3.3 s (snapshot, create, daemon ready); the rest of the 21.8 s vm fork was dev-server route compilation. A paused source is now snapshotted without waking it, and the fork route returns Server-Timing with a provider_snapshot stage.

Also: forks inherit the base machine's size, network, and agent settings (shown locked in the sheet), and origin/main is merged with the string catalog resolved per key.

Changelog

Added: New Machine can start from an existing machine.
Fixed: Forked Cloud machines now start and attach, and Fork shows a pending "Forking…" row and workspace immediately from every entrypoint.

Validation

  • bun test tests/vm-devbox-remote-state.test.ts red (module missing) at b4057cff864, green after; readiness tests cover the stale-listener race.
  • bun test remote-state, image-manifest, freestyle-provider, model-plane-workflow, resource-pool: 111 pass. tsc clean for touched files, bun run lint:complexity clean, python3 scripts/lint-xcstrings.py passes.
  • Live: fork on tagged backend cloud-fork-fix-v5 succeeded and attached (vm fork → OK workspace=…).
  • Swift tests added (MachineCreateCoordinatorTests, CloudMenuContentTests, NewMachineModelTests) run in CI only; not run locally.
  • Pending: GUI dogfood of the pending row on build cloud-fork-optimistic-v6.
  • Review subagents ran on the backend and client diffs; findings (supervisor race, restore origin, fork retry idempotency, menu window) are fixed.

— Mochi (callsign pending registrar receipt)

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Adds a base image picker to the New Machine sheet so cloud users can create a machine from the default image or fork an existing one, with the new strings translated in nine languages. Forking now works from every entrypoint and shows a pending "Fork of …" row and reserved workspace before any work.

Forks inherit the base machine's size, network, and agent settings; the controls stay visible but locked, with an inheritance note. Snapshot-based forks copy a paused source without waking it first. The client shows Fork whenever a provider supports a native fork or snapshot plus restore (canFork); the server's native-fork flag stays unmodified. vm fork accepts --workspace, prints an OK machine= receipt, and uses a store-based idempotency key so a retry joins the first attempt. The fork route opens the provider pool after auth and defers stats reads, ledger rows, and success events until after the response; dev prewarming compiles the fork and attach routes.

Bug Fixes

  • Forked guests now repair a stranded remote session and wait for the clone's own daemon before reporting ready, so the clone becomes attachable.

Summary by CodeRabbit

  • New Features
    • Create a machine from an existing machine using the base-image option, or choose the default image.
    • Forked machines inherit the base machine’s size, network, and agent settings. The New Machine sheet displays an inheritance note and disables those settings for forks.
    • Freestyle machines support forking, with fresh connection state on the new machine.
    • The cmux vm fork command accepts an optional workspace ID, reference, or index.
    • Base-image settings and related guidance are available in nine languages.