fix(cloud): keep renamed workspace projections in lockstep

Merged
AU
2mo ago
+265-0

What changed

Cloud workspace names now have one authoritative propagation path. SurfaceCatalog owns the
cloud workspace identity/name graph and fans a rename out to machine metadata, every nested
resource view, restored projections, the Cloud tree, and local right-sidebar workspaces.

The provider submits renames with a generation/revision compare-and-swap fence and serializes
requests per stable (machine id, workspace id). Accepted snapshots/deltas are reconciled as
complete graphs; older generations, regressive revisions, malformed snapshots, and equal-cursor
payload mismatches are rejected. Cursorless provider metadata cannot restore an older name, and a
stale equal-cursor read after a mutation receipt is rejected.

Cloud and local projected workspaces persist the exact cmux-tui workspace id through session
restore. Local workspace title edits, the Cloud sidebar Rename action, socket
vm.workspace_rename, and cmux vm workspace rename all call the same provider/catalog path.
Duplicate names remain addressable by stable ids; ambiguous or detached placements fail closed.

Related context: https://github.com/manaflow-ai/cmux/issues/11762

Regression coverage

Behavior-level coverage includes:

  • immediate fan-out to machine metadata, resource views, and the right-sidebar projection;
  • stale generations, regressive/equal cursors, reconnects, and uncertain failures;
  • queued rename ordering and rollback fencing;
  • duplicate-name/id resolution, malformed graph handling, stable binding persistence;
  • cursorless metadata writes and stale equal-cursor payloads after a rename receipt;
  • cmux-tui argument/parser behavior and the existing workspace/tree projection suites.

The regression history is intentionally split into test-only commits followed by fixes so CI can
prove the tests catch the race.

Verification

  • swiftc -parse on every changed Swift file: passed.
  • ./scripts/lint-pbxproj-test-wiring.sh: passed (764 test files).
  • python3 scripts/check-workspace-package-groups.py --check: passed.
  • python3 scripts/check-package-resolved-policy.py: passed.
  • ./scripts/check-pbxproj.sh and git diff --check: passed.
  • Focused fleet test run on a GUI-capable Mac (cmux-unit, 45 tests in
    CloudWorkspaceRenameReconciliationTests and SurfaceCatalogTests): 45 passed, 0 failed.
    Command used the remote cmux account with the app-host wrapper because the generic xctest
    helper currently selects cmuxvnc* accounts that are not authorized in this inventory.
  • A broader four-suite fleet run reached 117 tests; the three rename/catalog/provider suites
    passed. The upstream WorkspaceGroupTests.staleGroupReferenceInsideGroupRunRendersAsRootRow
    assertion from origin/main failed independently of this change.
  • Tagged merged-HEAD build/launch: CMUX_SKIP_ZIG_BUILD=1 CMUX_DEV_BACKEND_MODE=local CMUX_DEV_WEB_PORT_OVERRIDE=4515 /Users/austinwang/manaflow/cmuxterm-hq/scripts/reload-cloud.sh --tag issue-11762-cloud-rename-sidebar --launch (fleet build succeeded).

Live verification

OrbStack is running and the isolated local web server is persistent in tmux session
cmux-web-issue-11762:

  1. orb status → Running; Docker context → orbstack.
  2. http://localhost:4515/ → HTTP 200; /api/vm → HTTP 401 without auth (expected).
  3. Authenticate the tagged build, create two workspaces in one Freestyle machine, rename one
    from the Cloud workspace row, and confirm the renamed label in both the Cloud Workspaces group
    and the right-sidebar local projection.
  4. Refresh/reconnect/reopen the machine/workspace and verify the renamed workspace remains changed
    and the second workspace is unchanged; repeat with cmux vm tree --json and
    cmux vm workspace rename for CLI parity.

The live two-workspace UI run is blocked in this environment: the local 4515 database has a
machine row, but Freestyle already owns the deterministic tunnel slug for this Mac in the
separate all-agents backend (port 9170). The safe API response is HTTP 502 conflict; I did not
revoke or overwrite the active tunnel. The existing production tunnel is also for a different
VPC. Exact retry path: point the tagged app/backend at the account backend that owns the tunnel,
or have an operator reconcile the local dev tunnel record, then run the four steps above.

Trade-offs / limitations

  • The fan-out remains model-owned in SurfaceCatalog; no view keeps an optimistic duplicate.
  • A legacy cursorless daemon remains readable but cannot be safely renamed until a versioned
    snapshot is observed.
  • When a concurrent writer wins a CAS race, the request reports a user-safe conflict and keeps
    the newer canonical name rather than guessing or retrying over it.
  • No local xcodebuild test or XCUITest was run, per repository policy; tests used the shared Mac
    fleet. The repository's scripts/swift_file_length_budget.py and
    .github/swift-file-length-budget.tsv are absent on this branch, so that requested check could
    not be executed; no budget files were modified.
  • No production tunnel or cloud VM was deleted or overwritten while diagnosing the local server.

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

Cloud workspace renames previously left machine metadata, resource views, the Cloud tree, and right-sidebar projections out of sync. Renames now route through SurfaceCatalog with a generation/revision compare-and-swap fence, serialized per (machine id, workspace id), so every projection stays in lockstep.

Stale generations, regressive revisions, malformed snapshots, and equal-cursor payload mismatches are rejected. All rename paths (local title edit, Cloud sidebar Rename, vm.workspace_rename socket, cmux vm workspace rename CLI) share the same provider/catalog path, and the stable cmux-tui workspace id persists through session restore. Addresses issue #11762.

Migration

  • workspace.cloud_vm_bind now accepts an optional remote_workspace_id so renames stay addressable after restore.
  • Legacy cursorless daemons remain readable but cannot be renamed until a versioned snapshot is observed.
  • When a concurrent writer wins a CAS race, the request fails with a user-safe conflict and keeps the newer canonical name.

Summary by CodeRabbit

  • Tests
    • Added coverage for cloud workspace rename synchronization across related views and metadata.
    • Verified that confirmed names are protected from stale, delayed, or cursorless updates.
    • Added checks ensuring newer rename changes remain intact when older updates are rolled back.