Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
f40f0eb
Cloud VM startup benchmarks: control plane, provider floor, private link
austinywang Sep 18, 2026
ca41f28
Cloud startup latency report: baseline, lower bound, ranked plan (#12…
austinywang Sep 18, 2026
c3c6312
Merge remote-tracking branch 'origin/main' into 12905-cloud-startup-l…
austinywang Sep 18, 2026
2019e57
Report: count the attach-time shim/opener check added by #12741
austinywang Sep 18, 2026
8b64885
Benchmarks: fail closed on cleanup, readiness, statuses, and interrupts
austinywang Sep 18, 2026
057872f
Benchmarks: bound every wait by its budget, reject no-op runs
austinywang Sep 18, 2026
deb7863
API benchmark: never orphan a create, prove each readiness sample
austinywang Sep 18, 2026
7dc5c29
Benchmarks: reconcile by run identity so a lost create never orphans …
austinywang Sep 18, 2026
e6821a0
Cloud: make new machine creation optimistic
austinywang Sep 18, 2026
cef7037
Benchmarks: no public daemon ingress, provable readiness, full accoun…
austinywang Sep 18, 2026
26fd7e8
Report: incidental findings (double allocation, owner-network leaks, …
austinywang Sep 18, 2026
26b9a0f
Benchmarks: verify provider inventory, size the session, page and fai…
austinywang Sep 18, 2026
a4c1700
Benchmarks: keep partial inventory, page to totalCount, read the acco…
austinywang Sep 18, 2026
96ca146
API benchmark: keep the user after an unclassified account failure, b…
austinywang Sep 18, 2026
0a39687
Benchmarks: bound SDK awaits, stop the prompt clock before teardown, …
austinywang Sep 18, 2026
07cecb4
Benchmarks: reconcile networks and tunnels by slug, delete a session-…
austinywang Sep 18, 2026
0dd67ba
Benchmarks: settle in-flight provider requests before reconciling; wa…
austinywang Sep 18, 2026
2a6884c
Benchmarks: keep the identity for the app's resumable account deletio…
austinywang Sep 18, 2026
5ae92f4
Benchmarks: fail closed on an unknown identity; never abandon in-flig…
austinywang Sep 18, 2026
6d217f8
Benchmarks: resolve ambiguous creates, bound every teardown SDK call,…
austinywang Sep 18, 2026
b521e3a
Benchmarks: interruptible finalizer timeouts, bounded attach, typed v…
austinywang Sep 18, 2026
d8f8582
Benchmarks: give the transport benchmark production's inputs; fix the…
austinywang Sep 18, 2026
3e64976
Benchmarks: accept the runtime's provider credential forms, fill sens…
austinywang Sep 18, 2026
98805d4
Benchmarks: verify a --url deployment belongs to the project before s…
austinywang Sep 18, 2026
7ca7c62
Benchmarks: prove slug ownership before any slug-based deletion
austinywang Sep 18, 2026
d0eb458
Benchmarks: fail closed on a slug conflict; time only the daemon boun…
austinywang Sep 18, 2026
390c7c8
Report: cite the re-measured allocation in the transport narrative an…
austinywang Sep 18, 2026
e600a94
Benchmarks: slug recovery only for a lost create, unresolved creates …
austinywang Sep 18, 2026
c3febf3
Benchmarks: delete the known network again after recovering a lost tu…
austinywang Sep 18, 2026
06fd621
Benchmarks: re-sweep machines after settling; a tunnel recovered unde…
austinywang Sep 18, 2026
766c7f9
Benchmarks: only a conflict proves a create did not happen; never exi…
austinywang Sep 18, 2026
53da7f3
Benchmarks: never take an empty user search as proof after a lost cre…
austinywang Sep 18, 2026
e8c46a1
Benchmarks: probe the deployment's edge alias; scope the transport be…
austinywang Sep 18, 2026
8955101
Benchmarks: never sweep past an in-flight provider request; run a fai…
austinywang Sep 18, 2026
c1b5b69
Benchmarks: bound the API benchmark's provider client; previews need …
austinywang Sep 18, 2026
48691b9
Benchmarks: refuse an image without the identity marker; report: the …
austinywang Sep 18, 2026
e7b4868
Benchmarks: name the create-plus-attach sum for what it is
austinywang Sep 18, 2026
f7bcea0
Benchmarks: bound every ambiguous-create re-post by the route's remai…
austinywang Sep 18, 2026
7c4231a
Benchmarks: bound the Stack calls; mark a run's network independently…
austinywang Sep 18, 2026
18f67e0
Benchmarks: a 5xx create is ambiguous; size the session to the reques…
austinywang Sep 18, 2026
7ee01a0
Benchmarks: one bounded provider client for all three; resolve create…
austinywang Sep 18, 2026
f224da0
Benchmarks: an abandoned background request is unresolved cleanup; de…
austinywang Sep 18, 2026
d543449
Benchmarks: a background request is outstanding until its answer is s…
austinywang Sep 18, 2026
d0a200d
Benchmarks: record a created network's and tunnel's id on the request…
austinywang Sep 18, 2026
cfbf49d
Merge origin/main into optimistic machine creation
austinywang Sep 19, 2026
a1610d7
test: expose pending machine identity and recursive close regressions
austinywang Sep 19, 2026
8a19388
Merge branch 'main' into 12905-cloud-startup-latency
lawrencecchen Sep 19, 2026
d2ad79f
fix: own optimistic machine lifecycle and stable adoption in Cloud pa…
austinywang Sep 19, 2026
2f117d8
test: unblock hosted verification and refresh merged iOS fixtures
austinywang Sep 19, 2026
134efbd
ci: route Cloud lifecycle tests through the shared runner setting
austinywang Sep 19, 2026
4387f23
test: expose machine placement deferred until provisioning completes
austinywang Sep 19, 2026
cf0892b
fix: place new machine reservations before asynchronous provisioning
austinywang Sep 19, 2026
a64d338
Merge remote-tracking branch 'origin/pr-12911' into 12904-optimistic-…
austinywang Sep 19, 2026
f94d2bf
test: require create-time naming and idempotent display-name persistence
austinywang Sep 19, 2026
adc24fb
fix: return the extracted Base loading content
austinywang Sep 19, 2026
39ac104
perf: name machines during create and reuse matching client artifacts…
austinywang Sep 19, 2026
47d6f83
fix: pass reserved machine name into provider create
austinywang Sep 19, 2026
8c16a11
test: type check create-time naming coverage
austinywang Sep 19, 2026
18d63e1
refactor: keep Cloud VM create complexity within the gate
austinywang Sep 19, 2026
2bb9aa1
test: require an attested cmux-tui manifest before installing a publi…
austinywang Sep 19, 2026
ef3fe4a
ci: verify the published cmux-tui manifest's build-provenance attesta…
austinywang Sep 19, 2026
131581d
test: expect a localized vm_invalid_request for an unusable display name
austinywang Sep 19, 2026
522d2db
fix: localize the Cloud VM display-name rejection and answer it as vm…
austinywang Sep 19, 2026
abdf704
test: pin the profile-expiry clock, drop the CLI naming wall-clock ce…
austinywang Sep 19, 2026
891ac21
Merge origin/main (9f29ddfc77a) into 12904-optimistic-machine-creation
austinywang Sep 19, 2026
f3c4d46
test: expect every remote cmux-tui install to verify the publisher's …
austinywang Sep 19, 2026
01cb4b3
ci: verify the cmux-tui manifest attestation on every remote install …
austinywang Sep 19, 2026
6f16e1a
Merge branch 'main' into 12904-optimistic-machine-creation
lawrencecchen Sep 19, 2026
f819895
test: keep the create-time naming test importable on Python 3.9
austinywang Sep 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion .github/scripts/install-app-store-provisioning-profile.sh
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ validate_extension_profile() {
fi
if ! python3 - "$plist_path" "$EXPECTED_CERT_SHA256" <<'PY'
import hashlib
import os
import plistlib
import sys
from datetime import datetime, timezone
Expand All @@ -116,8 +117,18 @@ if entitlements.get("get-task-allow") is not False:
raise SystemExit("profile is not an App Store distribution profile")
if profile.get("ProvisionsAllDevices") or "ProvisionedDevices" in profile:
raise SystemExit("profile is not an App Store distribution profile")
# Tests inject a fixed instant so a fixture never depends on the real clock;
# the release lanes leave it unset and validate against now.
fixed_now = os.environ.get("IOS_APPSTORE_PROFILE_VALIDATION_TIME", "")
if fixed_now:
now = datetime.fromisoformat(fixed_now.replace("Z", "+00:00"))
if now.tzinfo is None:
raise SystemExit("IOS_APPSTORE_PROFILE_VALIDATION_TIME must carry a timezone offset")
now = now.astimezone(timezone.utc)
else:
now = datetime.now(timezone.utc)
expiration = profile.get("ExpirationDate")
if not isinstance(expiration, datetime) or expiration.astimezone(timezone.utc) <= datetime.now(timezone.utc):
if not isinstance(expiration, datetime) or expiration.astimezone(timezone.utc) <= now:
raise SystemExit("profile is expired")
if expected_cert:
fingerprints = {
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1874,6 +1874,7 @@ jobs:
CMUXAuthCore
CmuxBrowser
CmuxCanvasUI
CmuxCloudMachines
CmuxCore
CmuxRemoteDaemon
CmuxRemoteWorkspace
Expand Down Expand Up @@ -2710,6 +2711,10 @@ jobs:
path: ghostty-cli-helper

- name: Install universal Ghostty CLI helper
env:
# install-cmux-tui-client.sh verifies the manifest's build-provenance
# attestation with gh before trusting it.
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
/bin/bash ./tests/test_install_cmux_tui_client.sh
Expand Down
39 changes: 39 additions & 0 deletions .github/workflows/cloud-machine-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Cloud machine lifecycle

on:
pull_request:
paths:
- Packages/macOS/CmuxCloudMachines/**
- .github/workflows/cloud-machine-tests.yml
workflow_dispatch:
inputs:
runner:
description: Optional hosted macOS runner label
required: false
default: ""
type: string
ref:
description: Source ref to test
required: false
default: ""
type: string

permissions:
contents: read

concurrency:
group: cloud-machine-tests-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
lifecycle:
runs-on: ${{ inputs.runner || vars.MACOS_RUNNER_15 || 'blacksmith-6vcpu-macos-15' }}
timeout-minutes: 10
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ inputs.ref || github.ref }}
- name: Select Xcode
run: ./scripts/select-ci-xcode.sh
- name: Test the package without launching the app
run: swift test --package-path Packages/macOS/CmuxCloudMachines -Xswiftc -warnings-as-errors
4 changes: 4 additions & 0 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,10 @@ jobs:
# Bundle once here, not per sign variant, so every variant carries the same
# cmux-tui build and the download happens a single time.
- name: Bundle the cmux-tui client
env:
# install-cmux-tui-client.sh verifies the manifest's build-provenance
# attestation with gh before trusting it.
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
app="build-universal/Build/Products/Release/cmux.app"
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,10 @@ jobs:

- name: Install universal Ghostty CLI helper
if: steps.guard_release_assets.outputs.skip_all != 'true'
env:
# install-cmux-tui-client.sh verifies the manifest's build-provenance
# attestation with gh before trusting it.
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
./scripts/install-prebuilt-ghostty-cli-helper.sh \
Expand Down
68 changes: 29 additions & 39 deletions .github/workflows/reload-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,56 +113,41 @@ jobs:
echo "cmux-cua build script absent in source ref; skipping Rust provisioning"
fi

# The app's Cloud machine path requires the wireguard-hub cmux-tui
# capability. Do this before Xcode or cache work, and pass the exact
# source-built binary into reload.sh. A rolling download can lag the app
# source and otherwise fails only after the full macOS build completes.
- name: Install Zig for bundled cmux-tui (macOS)
# Reuse an immutable client built from the exact cmux-tui/Ghostty inputs.
# App-only changes do not require recompiling cmux-tui on a Mac.
# The artifact host is not trusted: the manifest must carry the Sigstore
# build-provenance attestation that cmux-tui-artifacts.yml signs for this
# exact source commit before the installer reads a hash from it or the
# client runs (`remote-probe` inside the installer, then below).
- name: Install and validate matching published cmux-tui client (macOS)
if: ${{ inputs.platform == 'macos' && hashFiles('cmux-tui/Cargo.toml') != '' }}
run: ./scripts/install-zig-ci.sh

- name: Build and validate exact cmux-tui client (macOS)
if: ${{ inputs.platform == 'macos' && hashFiles('cmux-tui/Cargo.toml') != '' }}
working-directory: cmux-tui
env:
CARGO_TARGET_DIR: ${{ runner.temp }}/cmux-tui-target
CMUX_TUI_DISTRIBUTION_VERSION: 0.0.0-dev-${{ inputs.tag }}
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
export PATH="$HOME/.cargo/bin:$PATH"
source_sha="$(git -C .. rev-parse HEAD)"
ghostty_sha="$(git -C ../ghostty rev-parse HEAD)"
export CMUX_TUI_BUILD_COMMIT="$source_sha"
export CMUX_TUI_GHOSTTY_COMMIT="$ghostty_sha"
target="$(rustc -vV | awk '/^host: / { print $2 }')"
case "$target" in
aarch64-apple-darwin|x86_64-apple-darwin) ;;
*) echo "unsupported macOS Rust host target: $target" >&2; exit 1 ;;
esac
cargo build -p cmux-tui --bin cmux-tui --release --locked --target "$target"
client="$CARGO_TARGET_DIR/$target/release/cmux-tui"
test -x "$client"
client_commit="$(scripts/ci/resolve-cmux-tui-client-commit.sh)"
git diff --exit-code "$client_commit" HEAD -- cmux-tui ghostty
client_bundle="$RUNNER_TEMP/cmux-tui-prebuilt"
mkdir -p "$client_bundle/Contents"
env -u CMUX_TUI_CLIENT_LOCAL scripts/install-cmux-tui-client.sh "$client_bundle" \
--manifest-url "https://files.cmux.com/cmux-tui/$client_commit/manifest.json" \
--expected-commit "$client_commit" --require-capability wireguard-hub \
--attest-signer-workflow "$GITHUB_REPOSITORY/.github/workflows/cmux-tui-artifacts.yml"
client="$client_bundle/Contents/Resources/bin/cmux-tui"
probe="$($client remote-probe --json)"
version="$($client --version)"
PROBE="$probe" VERSION="$version" EXPECTED_COMMIT="$source_sha" EXPECTED_GHOSTTY="$ghostty_sha" python3 - <<'PY'
ghostty_sha="$(git -C ghostty rev-parse HEAD)"
PROBE="$probe" VERSION="$version" EXPECTED_COMMIT="$client_commit" EXPECTED_GHOSTTY="$ghostty_sha" python3 - <<'PYVERIFY'
import json
import os

probe = json.loads(os.environ["PROBE"])
version = os.environ["VERSION"]
expected = os.environ["EXPECTED_COMMIT"]
expected_ghostty = os.environ["EXPECTED_GHOSTTY"]
if probe.get("build_identity") != expected:
raise SystemExit(
f"cmux-tui build identity {probe.get('build_identity')!r} != {expected!r}"
)
if "wireguard-hub" not in probe.get("capabilities", []):
raise SystemExit("cmux-tui client lacks wireguard-hub capability")
if expected_ghostty not in version:
raise SystemExit("cmux-tui client does not carry the checked-out Ghostty identity")
PY
if probe.get("build_identity") != os.environ["EXPECTED_COMMIT"]:
raise SystemExit("published cmux-tui client has the wrong build identity")
if os.environ["EXPECTED_GHOSTTY"] not in os.environ["VERSION"]:
raise SystemExit("published cmux-tui client has the wrong Ghostty identity")
PYVERIFY
echo "CMUX_TUI_CLIENT_LOCAL=$client" >> "$GITHUB_ENV"
echo "cmux-tui ready: $client"

- name: Prepare macOS cache metadata
if: ${{ inputs.platform == 'macos' }}
Expand Down Expand Up @@ -561,6 +546,11 @@ jobs:
app_path="$(awk '/^App path:/{getline; sub(/^ /,""); print; exit}' "$log")"
[ -n "$app_path" ] && [ -d "$app_path" ] || { echo "could not locate built app" >&2; exit 1; }
echo "app_path=$app_path" >> "$GITHUB_OUTPUT"
if [ -f tests/test_cli_vm_create_name.py ]; then
CMUX_CLI_BIN="$app_path/Contents/Resources/bin/cmux" \
DYLD_FRAMEWORK_PATH="$app_path/Contents/Frameworks:$derived_data/Build/Products/Debug" \
python3 tests/test_cli_vm_create_name.py
fi
mkdir -p artifact
( cd "$(dirname "$app_path")" && ditto -c -k --sequesterRsrc --keepParent "$(basename "$app_path")" "$GITHUB_WORKSPACE/artifact/app.zip" )

Expand Down
10 changes: 9 additions & 1 deletion .github/workflows/test-depot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ name: Run tests on Depot
on:
workflow_call:
inputs:
runner:
type: string
default: ""
ref:
type: string
default: ""
Expand All @@ -23,6 +26,11 @@ on:
default: "120"
workflow_dispatch:
inputs:
runner:
description: Optional hosted macOS runner label
required: false
default: ""
type: string
ref:
description: Branch or SHA to test
required: false
Expand Down Expand Up @@ -52,7 +60,7 @@ on:

jobs:
tests:
runs-on: ${{ vars.MACOS_RUNNER_15 || 'blacksmith-6vcpu-macos-15' }}
runs-on: ${{ inputs.runner || vars.MACOS_RUNNER_15 || 'blacksmith-6vcpu-macos-15' }}
timeout-minutes: ${{ inputs.unit_test_suites != '' && 35 || 20 }}
steps:
- name: Clear stale git locks (self-hosted reused workspace)
Expand Down
36 changes: 18 additions & 18 deletions CLI/cmux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ private func agentHookDebugLogPath(socketPath: String?, env: [String: String]) -
if let explicit = agentHookDebugNonEmpty(env["CMUX_DEBUG_LOG"]) {
return NSString(string: explicit).expandingTildeInPath
}

if let socketPath {
let socketName = URL(fileURLWithPath: socketPath).lastPathComponent
if socketName.hasPrefix("cmux-debug-"), socketName.hasSuffix(".sock") {
Expand Down Expand Up @@ -4204,12 +4203,12 @@ struct CMUXCLI {
return VMMachineKind.defaultKind
}
private static let cloudVMDesktopPort = 6901
/// `vm shell <id>` and `vm open <id>`: the shared cloud open path through the
/// machine's cmux-tui remote daemon. Desktop panes are opened explicitly.
/// Opens the machine shell through cmux-tui, honoring explicit background attachment.
func openVMWorkspaceShell(
vmId: String,
windowRaw: String?,
targetWorkspaceId: String?,
focus: Bool = true,
client: SocketClient,
jsonOutput: Bool,
idFormat: CLIIDFormat
Expand All @@ -4220,7 +4219,7 @@ struct CMUXCLI {
windowRaw: windowRaw,
targetWorkspaceId: targetWorkspaceId,
forceSSH: false,
shouldPinWorkspaceToTop: false,
shouldPinWorkspaceToTop: false, focus: focus,
client: client,
jsonOutput: jsonOutput,
idFormat: idFormat
Expand Down Expand Up @@ -4452,14 +4451,17 @@ struct CMUXCLI {
return directAgentKeys.contains { normalizedEnvValue(environment[$0]) != nil }
}

private static func vmCreateIdempotencySignature(image: String?, provider: String?) -> String {
private static func vmCreateIdempotencySignature(image: String?, provider: String?, workspace: String?) -> String {
let normalizedImage = image?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
let normalizedProvider = provider?
.trimmingCharacters(in: .whitespacesAndNewlines)
.lowercased() ?? ""
return "image=\(normalizedImage)\u{1f}provider=\(normalizedProvider)"
let normalizedWorkspace = workspace?
.trimmingCharacters(in: .whitespacesAndNewlines)
.lowercased() ?? ""
if normalizedWorkspace.isEmpty { return "image=\(normalizedImage)\u{1f}provider=\(normalizedProvider)" }
return "image=\(normalizedImage)\u{1f}provider=\(normalizedProvider)\u{1f}workspace=\(normalizedWorkspace)"
}

private static func normalizedVMProvider(_ provider: String?) throws -> String? {
guard let trimmed = provider?.trimmingCharacters(in: .whitespacesAndNewlines),
!trimmed.isEmpty else {
Expand All @@ -4474,11 +4476,8 @@ struct CMUXCLI {
}
return normalized
}

private static func isFlagToken(_ value: String) -> Bool { value.hasPrefix("-") && value != "-" }

private static func isUnknownFlagToken(_ value: String, allowedShortFlags: Set<String> = []) -> Bool { isFlagToken(value) && !allowedShortFlags.contains(value) }

private static func validatedVMSessionIdentifier(_ value: String?, flag: String) throws -> String? {
guard let trimmed = value?.trimmingCharacters(in: .whitespacesAndNewlines),
!trimmed.isEmpty else {
Expand Down Expand Up @@ -4517,9 +4516,9 @@ struct CMUXCLI {
try? fileManager.setAttributes([.posixPermissions: 0o600], ofItemAtPath: url.path)
}

private static func activeVMCreateIdempotency(image: String?, provider: String?) throws -> ActiveVMCreateIdempotency {
private static func activeVMCreateIdempotency(image: String?, provider: String?, workspace: String? = nil) throws -> ActiveVMCreateIdempotency {
let url = vmCreateIdempotencyStoreURL()
let signature = vmCreateIdempotencySignature(image: image, provider: provider)
let signature = vmCreateIdempotencySignature(image: image, provider: provider, workspace: workspace)
let now = Date().timeIntervalSince1970
var store = loadVMCreateIdempotencyStore(from: url)
store.records = store.records.filter { _, record in
Expand Down Expand Up @@ -5634,7 +5633,7 @@ struct CMUXCLI {
try openVMWorkspaceShell(
vmId: vmId,
windowRaw: windowOpt ?? windowId,
targetWorkspaceId: workspaceOpt,
targetWorkspaceId: workspaceOpt, focus: focus ?? true,
client: client,
jsonOutput: jsonOutput,
idFormat: idFormat
Expand Down Expand Up @@ -5791,8 +5790,7 @@ struct CMUXCLI {
memoryMb = nil
}
let remaining = rem3.filter { !["--detach", "-d", "--desktop", "--base", "--no-desktop"].contains($0) }
// The kind is what the CLI asks for; the backend picks the image. The
// machine gets its screen streamed into a browser split beside the shell.
// The backend resolves the machine kind to its image.
let machineName = nameOpt?.trimmingCharacters(in: .whitespacesAndNewlines)
if let unknown = remaining.first(where: { Self.isUnknownFlagToken($0, allowedShortFlags: ["-d"]) }) {
throw CLIError(message: """
Expand Down Expand Up @@ -5841,14 +5839,16 @@ struct CMUXCLI {
// not expose sizing ignore this optional field; providers that do use it
// for runtime memory get it, and the backend applies the plan ceiling.
if let memoryMb { params["memory_mb"] = memoryMb }
if let machineName, !machineName.isEmpty { params["display_name"] = machineName }
// Freestyle is the default and only deployed provider. It does not support
// persistent home volumes, so leave both volume flags out of this request.
let targetWindow = try validatedWindowHandle(windowOpt ?? windowId, client: client)
// Store-based idempotency: retries of a failed create reuse the key; a
// successful create clears it, so the next `vm new` makes a new machine.
let idempotency = try Self.activeVMCreateIdempotency(
image: imageOptRaw ?? "kind=\(machineKind.rawValue)",
provider: normalizedProvider
provider: normalizedProvider,
workspace: targetWorkspaceOpt
)
params["idempotency_key"] = idempotency.key
let vmCreateStartedAt = Date()
Expand Down Expand Up @@ -5885,8 +5885,8 @@ struct CMUXCLI {
let id = (response["id"] as? String) ?? "?"
let provider = (response["provider"] as? String) ?? "?"
let image = (response["image"] as? String) ?? "?"
// The label is display-only and best-effort: the machine exists either way.
if let machineName, !machineName.isEmpty {
// Older backends ignore create-time naming; preserve their rename behavior.
if let machineName, !machineName.isEmpty, response["displayName"] as? String != machineName {
_ = try? client.sendV2(
method: "vm.rename",
params: ["id": id, "display_name": machineName],
Expand Down
31 changes: 31 additions & 0 deletions Packages/macOS/CmuxCloudMachines/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,34 @@ let resources = CloudMachineResourcePresentation(
)
// resources.memory.percent == 50
```

`CloudMachineCreateCoordinator` owns pending creates, retry fences, cancellation
receipts, and adoption aliases. Reserve synchronously before launching I/O; feed
progress and completion back with the returned `CloudMachineCreateAttempt`. Apply
`CloudMachineCreateTransition` effects only after the state transition. The app
adapter owns processes, redaction, localized labels, notifications, and workspaces.
No package test needs to launch AppKit or a process:

```swift
let owner = CloudMachineCreateCoordinator(
output: CloudMachineCreateOutput(legacyCreatedFormat: "Created Cloud VM %@"),
now: { Date(timeIntervalSince1970: 123) }
)
let workspaceID = UUID()
let request = CloudMachineCreateRequest(
Comment thread
coderabbitai[bot] marked this conversation as resolved.
arguments: ["vm", "new", "--workspace", workspaceID.uuidString],
isBaseSetup: false, presentationWorkspaceID: workspaceID,
retainsPendingProjection: true
)
let attempt = owner.reserve(request)
// owner.projection already contains the pending row before starting the launcher.
let teardown = owner.cancelPresentations([workspaceID])
// teardown never requests another workspace close.
```

Adoption aliases persist for the account session, including after a successful
operation retires. This uses one small mapping per created machine so coalesced,
partial, and out-of-order panel refreshes cannot change row identity. Cancellation
tombstones remain until process termination instead of evicting live receipts.
Retries retain their original CLI idempotency scope; backend allocation durability
and the CLI's idempotency store remain outside this package.
Loading
Loading