Repository navigation
cmux-cloud-config: NEXT Cloud's config, auth and VM agent-key rules in Rust, over the app FFI and in the cmux vm CLI (step A) - #34982
Conversation
…n Rust, over the app FFI and in the cmux vm CLI (step A) The rules behind CloudConfiguration (the backend pick, the API origins, the Stack project, the token store's names, the VM API), the Auth folder's non-Keychain logic (DogfoodCredentials' account pick, the auth launch keys, ActiveTeam's refusal and managed-team pick) and AppControl+VMAgentKey (the agent-key request, its URL per backend and the answer's refusal codes) move into a new pure crate, cmux-cloud-config, with the Swift tests ported. cmux-layout-reducer-ffi's JSON channel serves them as cloud_config_*, cloud_auth_* and cloud_vm_agent_key_*. The cmux vm CLI links the same rules: cmux-vm's token create builds its body with agent_key::body, and cmux-tui's app-mint exit codes read agent_key::outcome. Step A of the two-step FFI move: step B re-pins the app FFI, makes the Swift side call these ops and deletes the Swift copies and their tests. No app behavior change here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-86478c59962584e11925
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
web and size checks: green Shipped in:
|
…T Cloud's Worker as their Cloud API, not the classic staging web app Configuration gains cloud_api_base_url, the base the app's machine client (CloudAPIClient) uses: the development owner API Worker for every development build (production keeps cmux.com; a debug build that names an origin keeps it). api_base_url stays the web app for sign-in, teams, account, billing, CodeRouter and Sandboxed runs, which have no cmux-platform route yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-86478c59962584e11925
|
Found 6 test failures on Blacksmith runners: Failures
|
…ice put in its text Takes the answer's own key and anything shaped like cmuxvm_sk_... out of the refusal message (review F1). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-86478c59962584e11925
|
Review (codex gpt-6.1-sol high): SHIP at 002c49a Findings: none. Follow-ups: none (F2-F4 from the first review at 207f611 are filed separately). |
…, the env-file quotes or the URL split (panic ratchet) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-86478c59962584e11925
…he two origins moved from Swift (keep, step B deletes the Swift copies) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-86478c59962584e11925
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
|
Review (codex gpt-6.1-sol high): SHIP at a836082 (carried from the SHIP at 660b640) Findings: none. Follow-ups: #35135 (F2-F4 from the review at 207f611). Finding outcome: board-review-207f611e/F1 fixed 002c49a |
…config-rust # Conflicts: # cmux-tui/Cargo.lock # cmux-tui/Cargo.toml
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
|
Review (codex gpt-6.1-sol high): SHIP at b619377 (carried from the SHIP at 660b640, via a836082) Findings: none. Follow-ups: #35135. Base red, not this PR's: |
…te guard (#35160) #35133 added cmux-tui/crates/cmux-cloud-environment/src/lib.rs with the cmux.com and cmux-staging origins after #35040 landed the guard, so tests/test_classic_cloud_routes.py is red on base. Add keep: entries like #34982's for cmux-cloud-config. Agent-Session: https://cmux.com/deeplink/agent?id=aid-86478c59962584e11925 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: cmux-bot[bot] <348531757+cmux-bot[bot]@users.noreply.github.com>
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Step 1 of moving NEXT Cloud's logic out of Swift into Rust, as the two-step app FFI move the repo uses for every new app FFI rule (step A here; step B re-pins the app FFI and switches Swift).
Leo, 2026-11-04, asked: "does the swift part need to be in/is it good for it to be in swift?" Shown the plan: "Yes, start it". Also Leo, 2026-11-04: "anything that we're pointing at cmux-classic related stuff (that isn't customer related) just point it away from classic".
What moves to Rust
A new pure crate,
cmux-tui/crates/cmux-cloud-config(serde only, no I/O), with the Swift tests ported:config: the backend pick ofCloudConfiguration(+Previews): environment, API origin, sign-in origin, Stack project and key, callback scheme, link source, the token store's names (Keychain service, file fallback path, defaults keys), the owner, share, preview and artifacts APIs, the share-API allowlist, the capture-build test and the VM API base (vm.cmux.dev/vm-staging.cmux.dev).auth: the Auth folder's logic that is not Keychain I/O:DogfoodCredentials' account pick (the app still reads the files and checks the explicit file's mode), the auth launch keys and their merge, the log mask, andActiveTeam's refusal and managed-team rules. Token storage, the Stack client and the team save stay in Swift (the token expiry and refresh logic lives in the sharedCmuxAuthRuntimepackage, outside this folder, and is not touched here).agent_key:vm.agent_key.create's request (VM scopes only, name default and cut, limits refused never dropped), its URL per backend, the answer (addsbaseUrl/teamId, never puts the key in a refusal) and the refusal codes' meaning for the CLI's exit status.cmux-layout-reducer-ffi's JSON channel (the app's one Rust library, the same channelcmux-cloud-start-coreuses) serves them ascloud_config_*,cloud_auth_*andcloud_vm_agent_key_*, with C ABI tests (tests/cloud_config.rs). The app keeps doing the HTTP call and the Keychain read; it sends facts and acts on the answer.The CLI shares the same code
The
cmux vmCLI is already Rust (workers/cmux-vm/cli, linked in process bycmux). Smallest design that truly shares the code: thecmux-vmcrate takes a path dependency oncmux-cloud-config, socmux vm token createwith its own credential (CMUX_VM_API_KEYholding a team key or a session token, plusCMUX_VM_TEAM_ID, no app) builds its body withagent_key::body, the exact function the app'svm.agent_key.createwill call in step B; andcmux's app-mint path maps refusals throughagent_key::outcome. No HTTP client goes into the app library (the app FFI links none), and no new binary.cmux-vm-clients.ymlnow also runs oncmux-tui/crates/cmux-cloud-config/**.Behaviour change in the CLI: an empty
--namenow gets the default name and a name over 200 characters is cut, as the app path already did.DEV repoint
Leo, 2026-11-04: "ok and so in future, we shouldnt need to get so confused by classic cloud, right? like we dont touch it but we dont have to keep calling the same routes... were doing new cloud with cmux-platform and stuff".
In the Rust rules, DEV, NEXT DEV and every other development build no longer use
https://cmux-staging.vercel.appas their Cloud API:Configuration.cloud_api_base_url(the base of the app's machine client,CloudAPIClient) is the development owner API Worker (https://cmux-api-development.debussy.workers.dev, NEXT Cloud, which forwards to cmux-platform). Production keepscmux.com; a debug build that names an origin (CMUX_VM_API_BASE_URL,CMUX_API_BASE_URL,CMUX_DEV_BACKEND_URL, local mode) keeps it. The owner API, the VM API (vm-staging.cmux.dev), previews and artifacts were already NEXT services. The app takes this in step B, whenCloudAPIClientreadscloudAPIBaseURL; this PR changes no app behaviour by itself. Test:no_development_build_uses_the_classic_staging_web_app_as_its_cloud_api.What stays on the staging web app (
api_base_url,auth_web_origin), because cmux-platform and cmux-vm have no route for it yet: the hosted sign-in pages (handler/native-sign-in,handler/after-sign-in,auth/callback), the team API (/api/teams, invites),/api/account,/api/device-tokens, CodeRouter accounts, billing, and Sandboxed runs (/api/runs, which needs a design on/v1/handoffs). The classic machine routes that DEV still sends throughCloudAPIClientwill hit the Worker after step B; the table below says which have an owner op to repoint to and which wait on a platform route (they already fail on the default link today, since they send NEXT machine ids to the classic app).CMUX_CLOUD_LINK=legacy/CMUX_NEXT_CLOUD_LINK=legacy: its only users are the.legacybranches listed under "Legacy-link only" below (nothing in CI, scripts or the capture rig sets it; the capture scene registry has a legacy-link scene). It andCloudClassicPageOpsget deleted, not moved, in the repoint PR; this PR keeps the rule as is so step A changes no behaviour.Deploys and publishes on merge (classic untouched)
Leo, 2026-11-04: "apparently the api if we change it it'll auto deploy so we wanna make extra sure we dont change it for clasisc". This PR changes nothing in
web/,backend/or any classic API:git diff --name-onlyhas no path there, andbackend.ymland the classic web deploys are not triggered. What a merge into feat-cmux-next does trigger, path by path:workers/cmux-vm/cli/**(the NEXT cmux-vm CLI crate) triggerscmux-vm.yml, whose push run redeploys the vm-staging Worker. The Worker bundle issrc/index.ts(wranglermain) and its TypeScript imports; nothing insrc/,wrangler.jsonc,package.jsonorscripts/deploy.shrefers tocli/, and no file underworkers/cmux-vm/outsidecli/changes, so that run ships an identical Worker (its check, smoke and rollback gates run as on any push).app-ffi-release.yml: a new path (cmux-tui/crates/cmux-cloud-config/**) and new FFI sources, so the merge publishes one newcmux-app-ffi-<sha>prerelease (make_latest false). That is the NEXT app's Rust library, which nothing links until step B pins it; the existing pin and every build are unchanged.cmux-vm-clients.yml: one new trigger path; its jobs only regenerate and test the cmux-vm CLI and SDK and upload CI artifacts. It never deploys or publishes.cmux-tui/Cargo.lock/Cargo.tomlchanges run the usual NEXT cmux-tui builds (cmux-tui artifacts, cmux-browser-host release, which publishes only on its own tag); same as any cmux-tui dependency change.Split (why this is not one PR)
CCmuxAppFFI, makeCloudConfiguration, the Auth helpers andAppControl+VMAgentKeycall the ops, delete the Swift copies and their tests.Classic
/api/vmand/api/runscalls in NEXTNEXT macOS app: calls to classic
/api/vmand/api/runsBase
e1869abf57c8(feat-cmux-next). cmux-platform atorigin/main393544a7.Paths below are relative to
Packages/macOS/CmuxNext/Sources/.Origins used in the "replacement" column:
CloudConfiguration.ownerAPIBaseURL():https://cloud-api.cmux.dev(prod),https://cmux-api-development.debussy.workers.dev(signed DEV and every non-prod build). Today this is cmux'sbackend/apps/apiWorker; cmux-platform has a ported copy (src/server/cloud/Ops.ts"ported from cmuxbackend/apps/api/src/http.ts",
MachineGuest.ts,AcpmuxRoute.ts). Ops go throughPOST /v1/ops(the app'sappOps/ credential relay); guest I/O is direct/v1/cloud/machines/{m}/.... These are not in platform'sopenapi.json(hand-rolled routes).openapi.json):cmux-platform-development|staging.debussy.workers.dev.NEXT reaches it only through
platformPreviewAPIBaseURL()(Debug + non-prod + appServer only; nil in Release/prod)and
artifactsAPIBaseURL().workers/cmux-vm, vm.cmux.dev / vm-staging.cmux.dev) is the provider layer cmux-platform itself calls(
src/server/cloud/vmapi/CloudProvider.ts,MeshMaintenance.ts,receiver/Native.ts). Its ids are provider VM ids,not the app's
vm_...machine ids, so it is never the direct repoint target from the app; it is listed only whereit holds the capability the platform has not yet exposed to owners.
Link source:
CloudConfiguration.resolvemakes.appServerthe default for every build (DEV, Debug, prod);.legacyonly withCMUX_NEXT_CLOUD_LINK=legacy/ bakedCMUX_CLOUD_LINK=legacy.cloud.apiis always theclassic client on
apiBaseURL(CloudService.swift:176);cloud.machineAPIis the Worker client on appServer andcloud.apion legacy (CloudService.swift:195). So anycloud.api.X(...)that is not behind a linkSource /session.linkguard sends a Worker
vm_...id tocmux-staging.vercel.app/cmux.com/api/vm/...on the default link: that is alive bug (marked unguarded below), not just legacy debt.
/api/vmlistMachinesAndLimits(); CloudService.swift:528 (legacy branch of refresh).listMachines():76 has no callers/v1/opscloud.machine.list(alreadyappServerMachines()); limits:cloud.plan.get(appServer setslimits = niltoday, so plan sizes are empty)listMachinesdelete/api/vm/{id}machine(_:); CloudHandlers+Machine.swift:291 (palette.cloud.status), :325 (palette.cloud.handoff) unguarded; CloudCodexBrokerTarget.swift:123 defaultisLive(legacy only, appServer passes its own)/v1/opscloud.machine.get/api/vmcreateMachineviacreateWithKey(CloudAPIClient+CreateWithKey.swift:14); CloudService.swift:790 (legacy branch of create)/v1/opscloud.machine.create(alreadyappServerCreate)/api/vm/{id}renameMachine; CloudService.swift:879 unguarded, from CloudHandlers+Machine.swift:162 and CloudAppOps.swift:143 (classic page backend)/v1/opscloud.machine.rename/api/vm/{id}deleteMachine; CloudService.swift:831 (legacy branch)cloud.machine.delete(already used)/api/vm/{id}/pausepauseMachine; CloudService.swift:844 (legacy branch)cloud.machine.pause(already,CloudMachinePower)/api/vm/{id}/resumeresumeMachine; CloudService.swift:852 (legacy branch)cloud.machine.start(already)/api/vm/{id}/idle-pausesetIdlePause; CloudService.swift:799 (after create of a persistent machine, both links) and :860 unguarded (CloudHandlers+Machine.swift:256cloudSetIdlePause)/v1/opscloud.machine.idle_policy.set/api/vm/{id}/attach-endpointattachEndpoint; CloudMachineLink.swift:212 (legacy link), CloudService.swift:284 (callerOwnsVMlegacy branch), CloudCodexBrokerTarget.swift:90 default (legacy only)cloud.machine.connect_info/cloud.machine.link_token; owner check =appLink.ownerLink(already)/api/vm/{id}/cmux-remote/approveapproveCmuxRemoteEnrollment; CloudMachineLink.swift:259 (legacy link)/api/vm/{id}/statsstats; CloudHandlers+Machine.swift:290palette.cloud.statusunguarded (allows(.stats)is true: appServer records declare no capabilities)/v1/machines/usageis billing usage, not live stats)/api/vm/{id}/resizeresize; CloudHandlers+Machine.swift:249 unguarded/v1/opscloud.machine.resize(PLAT/v1/machines/{id}/resizeis the team-machines family)/api/vm/{id}/snapshotsnapshot; CloudHandlers+Machine.swift:333 (snapshot), :353 (promote-template) unguarded/v1/opscloud.snapshot.create/api/vm/{id}/snapshotssnapshots; no callerscloud.snapshot.listif ever needed)/api/vm/{id}/snapshots/{sid}deleteSnapshot; CloudHandlers+Machine.swift:344 unguarded/v1/opscloud.snapshot.delete/api/vm/restorerestore(snapshotID:); CloudHandlers+Machine.swift:361 unguarded/v1/opscloud.snapshot.restore/api/vm/{id}/forkfork; CloudHandlers+Machine.swift:369 unguardedcloud.machine.forkop in Ops.ts; PLATPOST /v1/machines/{id}/forkexists but on the team-machines family (id namespace vsvm_...not verified)/api/vm/{id}/open-portopenPort; CloudHandlers+Browser.swift:85 (cloudMachineLink, no appLink guard), CloudHandlers+Machine.swift:240 (cloudCopyLink) unguardedPOST /v1/cloud/machines/{machine}/previews/{port}/links(+GET .../previews/{port}), viaplatformPreviewAPIBaseURL()= Debug non-prod only; app-localcloud.port.forward/cloud.browser.openfor local forwards/api/vm/{id}/acpmux/keyed-startacpmuxKeyedStart; CloudService+PersonKey.swift:104 (legacyApprovalsRoute,appLink == nil)POST /v1/cloud/machines/{m}/acpmux/keyed-start(already, CloudOwnerAPIClient)/api/vm/{id}/acpmux/person-holdsacpmuxPersonHolds; CloudService+PersonKey.swift:105 (legacy route)POST /v1/cloud/machines/{m}/acpmux/person-holds(already)/api/vm/{id}/scp-endpointprepareSCP; CloudHandlers+Machine.swift:131cloudPrepareSCPunguarded/v1/cloud/machines/{m}/client-session, a provider session, not an scp endpoint)/api/vm/tunnelenrollTunnel; CloudTunnelHub.swift:200. Hub built whenever the cmux-tui binary exists (CloudService.swift:258); used by legacy CloudMachineLink,startHubForSSHRoutesafter every list refresh when ssh routes exist (CloudService.swift:870),cloudEnrollMachine(CloudHandlers+Network.swift:79/114)GET /v1/host/network/target,PUT /v1/mesh/nodes/register(CloudNativeRouteHolder); backed by cmux-vm meshes/devices. No owner route that enrolls an ssh-route WireGuard hub/api/vm/tunnel?deviceId=revokeTunnel; CloudTunnelHub.swift:128 (sign-out)/v1/mesh/nodes/{nodeId}/revoke)/api/vm/tunnel/network/attach/detach/rotate-keyattachTunnelNetwork/detachTunnelNetwork/rotateTunnelKey; CloudHandlers+Network.swift:13 / :17 / :21 unguarded/v1/meshes/{id}/devices,/v1/devices/{id}/rotate-key, platform-internal)/api/vm/networklistNetworks; CloudHandlers+Network.swift:25 unguarded/v1/meshes, platform-internal)/api/vm/firewall[?vpcId..], GET?ruleId=, POST, DELETE?ruleId=listFirewallRules/getFirewallRule/createFirewallRule/deleteFirewallRule; CloudHandlers+Network.swift:32 / :40 / :58 / :47 unguarded/v1/meshes/{id}/acl, platform-internal)/api/vm/{id}/networknetworkPolicy/setNetworkPolicy; CloudHandlers+Network.swift:62-63 → CloudNetworkAccessActions.swift:24 guard== .legacycloud.vm.network_policyis the guest's own read; cmux-vmpublic-egressis platform-internal)/api/vm/domainslistDomains; CloudHandlers+Domains.swift:10 unguarded/api/vm/publicationslistPublications; CloudHandlers+Domains.swift:22 unguardedGET .../previews/{port},.../links)/api/vm/apisprovisionAPI; CloudHandlers+Domains.swift:44 unguardedhttps-proxies+api-keys, platform-internal; PLAT preview grants/links are the nearest owner routes)/api/vm/publications/{p}/api-keys[/{k}]listPublicationAPIKeys/revokePublicationAPIKey; CloudHandlers+Domains.swift:52 / :63 unguardedGET/DELETE .../previews/{port}/links[/{linkId}]is the nearest)/api/vm/{id}/chat-grants[/{user}]chatGrants/grantChat/revokeChat; CloudMachineShare.swift:30, :35, :48, :56; guarded bysession.link != nil(:28) and hidden on appServer (CloudHandlers+Machine.swift:200-203)PUT/DELETE /v1/machines/{id}/chat-grants/{user},GET /v1/machines/{id}/chat-access(caller's own access; no list-of-grants GET); team-machines id namespaceexec, GETfs/dir, GETfs/read, POSTfs/write, POSTfs/mkdir, DELETEfs/remove, GETfs/statundermachinePath(/api/vmon the classic client)exec/listFiles/readFile/writeFile/makeDirectory/removeFile/statFile; callers usemachineAPI(Worker on appServer) except CloudGitHubHandlers.swift:24cloud.api.exec(Set Up GitHub: classic route on appServer, a bug)/v1/cloud/machines/{m}/exec,/fs/*(already the Worker mode of the same methods)machineAPI; the classic mode goes with legacy/api/runscreateSandboxRun; AppControl+Cloud.swift:159 (runs.sandbox.create), also insiderunSandbox:392 /sandboxSnapshot:406 from AgentPageChats.swift:174 (Sandboxed send). All link sourcesPOST /v1/handoffs(NEXT already has CloudHandoffs.swift for list/wait); different model (ho_ids, turns, noagent/image)/api/runs/{id}/wait?timeoutMs=waitRun; AppControl+Cloud.swift:167, SandboxRunNotices.swift:31 (SandboxRunFollower, path label SandboxRunFollower.swift:48),runSandboxpollGET /v1/handoffs/{id}/wait/api/runs/{id}/cancelcancelRun; AppControl+Cloud.swift:174POST /v1/handoffs/{id}/cancelNot call sites (comments / error labels only): CloudTunnelHub.swift:199, CloudMachineLink.swift:262, CloudAPIClient.swift:243/401/420/423/430/434/438/536, SandboxRunNotices.swift:59, CloudStrings+Errors.swift:47, AgentTabs+Wiring.swift:244/451, CloudHandlers+Account.swift:88, CloudAppOps.swift:47, CloudService.swift:224/298/740/745/751, CloudNetworkAccessActions.swift:23, CloudHandlers+Machine.swift:200, CloudWorkspaceSetupWizard+Size.swift:5. CaptureSceneRegistry.swift:449-454 builds a legacy CloudMachineLink for capture scenes (goes with legacy). CloudClassicPageOps.swift makes no request itself: it runs the
CloudAppOps.swift:125-147closures (cloud.refresh/resume/pause/rename/delete) and is built only whenlinkSource == .legacy(PageFactory.swift:121-122).Notes
Blocked: no NEXT route yet (a repoint PR waits on a platform route)
stats(palette.cloud.status),fork,scp-endpoint.platformPreviewAPIBaseURL()is nil outside Debug non-prod).apis+ API keys, per-machine network policy: cmux-platform calls cmux-vm for all of these internally (vmapi/CloudProvider.tshttps-proxies,public-egress;MeshMaintenance.tsmeshes/devices) but exposes no owner route.POST/DELETE /api/vm/tunnel, used bycloud enroll-machineandstartHubForSSHRoutes): the Native route (/v1/host/network/target,/v1/mesh/nodes/register) covers the app link, not this.chat-access, but no GET that lists a machine's grants (CloudMachineShare's roster needs it). The team-machines/v1/machines/{id}id namespace is not verified to match the app'svm_...ids./api/runs(Sandboxed send,cmux run --sandboxed):/v1/handoffsis the nearest, but it needs a design, not just a new path.Ready to repoint now (the op exists on OWNER
/v1/ops, which the app already calls throughappOps/appServerApprovedCall):machineget (status, handoff) →cloud.machine.get; rename →cloud.machine.rename; idle pause →cloud.machine.idle_policy.set; resize →cloud.machine.resize; snapshot create/delete/restore →cloud.snapshot.*; Set Up GitHub exec →machineAPI. All of these are unguarded today, so on the default appServer link they send a Workervm_...id to the classic web app and fail there.Legacy-link only (delete with
.legacy)GET/POST
/api/vmlist and create, DELETE/api/vm/{id}, pause, resume, attach-endpoint, cmux-remote/approve, acpmux keyed-start / person-holds, network policy GET/PUT (guarded.legacy), chat grants (guardedsession.link), the classic mode of exec/fs, CloudClassicPageOps, the CloudMachineLink half of the tunnel hub, CaptureSceneRegistry's legacy link. Dead code:listMachines(),snapshots(_:).Sign-in / auth on the classic web origin
CloudConfiguration.signInURL(CloudConfiguration.swift:268-271):authWebOrigin/handler/native-sign-inwrappinghandler/after-sign-in→<scheme>://auth-callback. Served by classicweb/app/handler/{native-sign-in,after-sign-in}. cmux-platform has no equivalent: its/oauth/authorize|token|register|revokeare MCP OAuth, and/v1/auth/challenge|token+/.well-known/jwks.jsonare install-key auth, not a hosted Stack sign-in page.AuthConfigin CloudAuth.swift:104-108 and :178-182:magicLinkCallbackURL = authWebOrigin/auth/callbackandapiBaseURL = configuration.apiBaseURL. In CmuxAuthRuntime thatapiBaseURLis the cmux web API forGET/POST/PATCH/DELETE /api/teams[...]and invitations (CmuxTeamsAPI.swift; used by CloudAuth+Teams.swift: team list/create/detail/invite, the Accounts page), plus/api/account(AccountDeletionClient),/api/auth/email-verificationand/api/billing/recover(EmailVerificationRecoveryClient),/api/device-tokens(PushRegistrationService). cmux-platform serves none of/api/teams,/api/accountor/api/device-tokens.Other things on
CloudConfiguration.apiBaseURL/authWebOriginbesides/api/vmIf DEV's
apiBaseURLmoves offcmux-staging.vercel.app, all of these break or change too:AuthConfig.apiBaseURL.CodeRouterClient(baseURL: apiBaseURL)→/api/coderouter/accounts,/api/coderouter/claude-upstream[/{id}],/api/coderouter/vm-usage/team(AppControl+Accounts.swift:30-58, CodeRouterClient.swift:66-92). cmux-platform has only an internalCoderouterEdge.ts, no owner route.POST /api/billing/checkout(AppControl+Billing.swift:34, PaidPlanUpgrade.swift:70),GET /api/billing/plan(AppControl+Billing.swift:57), and theapiBaseURL/dashboard/billingportal page (AppControl+Billing.swift:47-48). OWNER has/v1/checkout/sessionsand/v1/billing/summary, andcloud.plan.getas an op, but not the same contract./api/runs(above).CloudMoveAsks.swift:234usesapiBaseURLas the scope key for stored "always allow" move grants (a new origin resets them).backend.apiBaseURL, for clarity: GitHub App status (CloudGitHubApp,/v1/integrations/github/status) usesownerAPIBaseURL(FeedService.apiBaseURL), but the Set Up GitHub exec itself goes through the classiccloud.api(row above). Feed, Hands, HomeCloudLease, ModelRouterLease, FreeTierAttest, HostedChief, host replica, credential relay and the app link all useownerAPIBaseURL.Testing
Through the fleet fast checks at the head (
cmux-ci test):cargo test -p cmux-cloud-config: 33 passed,clippy --all-targets -D warningsclean, and the crate's panic ratchet is clean (no string slicing, no lossy casts).cargo test --test cloud_configincmux-layout-reducer-ffi: 5 passed;cargo test -p cmux-tui --lib cli::vm: 39 passed.cargo test -p cmux-vminworkers/cmux-vm/cli, including the newthe_request_is_the_one_the_app_asks_for: passed.python3 tests/test_classic_cloud_routes.py(the classic-route guard from ci: guard NEXT code against new classic Cloud route calls #35040): passes. The two origins the config keeps (https://cmux.comfor production sign-in and teams,cmux-staging.vercel.appfor development sign-in and teams) moved fromCloudConfiguration.swiftand are allowlisted forconfig.rsaskeep:; step B deletes the Swift copies and theirdebt:entries. Neither is a Cloud API base for DEV or NEXT DEV any more.python3 tests/test_app_crate_licenses.py,scripts/cmux-next/tests/app-ffi-release-paths.test.sh: pass.Observed / Not run
Observed: the runs above. Not run here: the
cmuxbinary'scli::vmtests (CI), any Swift build (no Swift changes in step A). The app FFI pin check reports this PR's FFI sources as newer than the pin, as every step A does; step B re-pins.Follow-ups
Changelog
none
risk: normal (pure rules and tests; no app behaviour change until step B).
🤖 Generated with Claude Code
Agent session: agent session