Repository navigation
cmux NEXT: Cloud machine actions from the CLI need --target and return their result - #23155
Conversation
…used Red on base: CloudHandlers.machine falls back to the active window's machine, else the only Cloud machine, for any caller. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d3e-acc6-7a03-a574-453e3de27340&host=cosmic-gray
…nd answer with their result A CLI, MCP, script, page or remote run of a Cloud machine action no longer falls back to the machine the person's window shows (or the only machine): it must pass --target machine:<id>, and is refused with CloudStrings.noMachine otherwise. A person's own gesture keeps the fallback, and New Cloud Workspace keeps it for every caller. Non-person runs get their answer as action.run's result instead of a sheet on the person's screen: cloud exec runs the command through the Cloud API and returns exit_code/stdout/stderr; new-machine returns machine_id (or pending after 30 s); new-terminal-on-machine returns workspace_id; machine-status, ports, tools, hand-off, snapshot, promote-to-template, files-list, file-read, file-stat, prepare-scp, network-list, firewall-list/get, domain-list and publication-list return their facts. action.list/describe now say why an action is unavailable in the current context and which --target supplies it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d3e-acc6-7a03-a574-453e3de27340&host=cosmic-gray
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. |
|
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)
Not verified: macOS jobs did not run: compile and app tests were skipped on CI passes on Written by web and size checks: green Tour of |
|
Owned by projects-fa on cosmic-gray · session |
|
Owned by projects-fa on cosmic-gray · session |
This comment has been minimized.
This comment has been minimized.
|
Bead |
…d skill docs say --target is required A snapshot may take minutes: from the CLI, snapshot-machine and promote-machine-to-template answer pending after 30 s (inside action.run's 40 s result deadline) and the snapshot finishes either way, so an agent never reads a slow snapshot as a failure and retries into a second one. A non-person run nobody captures shows its failure as a sheet again. The cmux-cloud-vm skill now says machine actions from the CLI need --target and return their result. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d3e-acc6-7a03-a574-453e3de27340&host=cosmic-gray
This comment has been minimized.
This comment has been minimized.
…e CLI's origin; exec without API exec is refused The pending race awaited the work's Task.value inside a task group, which ignores cancellation, so the group waited for the whole snapshot and the run hit action.run's deadline instead of answering pending at 30 s. Both sides now yield into one AsyncStream and the first value wins (firstAnswer). CloudHandlers.machine, answer, exec and new-terminal read the enclosing ActionRunScope's origin, so a nested perform (built with the default user origin) inside a CLI run gets no window fallback. A CLI or agent cloud exec on a machine without API exec is refused with a localized reason instead of typing into a tab it cannot read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d3e-acc6-7a03-a574-453e3de27340&host=cosmic-gray
|
Review (independent subagent): SHIP at e7f28ea Re-review of the fixes for the NO-SHIP at 4d9d68f:
Not blocking: after The |
…d stream) Base's concurrency check now refuses a makeStream() with no buffering policy. The race yields at most twice, so .bufferingOldest(1) keeps the first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d3e-acc6-7a03-a574-453e3de27340&host=cosmic-gray
|
Review (independent subagent): SHIP at 76f0f18 This reviews only the commit after the earlier SHIP at e7f28ea (the head before that one is the clean base merge 25002314). It changes
The commit clears the new unbounded-AsyncStream crash-safety and concurrency check, which flagged this line at 4437807. |
|
|
…orget From re-review of 863062f: - A saved key no longer resends blindly: the app's answer to a key lives in memory (256 keys), so after a cmux restart a resend would show a second card and create a second paid machine. With a key on file setup reads cloud.machines first: a new creation that names its machine, or the one new machine, is its own; a new creation on its way is waited for; only when nothing new appeared is the create sent again with the same key. Setup also saves the creation ids there were. - An empty machines list (without loaded: true) while setup has a machine or a request on file is kind wait: nothing is cleared or created. A kept machine is gone only when missing from a non-empty or loaded list. The undo is now 'cmux cloud kill-machine --target machine:ID --confirm && cmux setup cloud-machine --forget'; --forget drops the state file and changes nothing in cmux. - The key is dropped only on the exact decline, busy, data.not_run, or a request that never left; any other refusal keeps it and names --forget. - One run at a time decides: an flock on cloud-machine.lock beside the state, held from reading the list through the create (libc, already a dependency). - A machine setup only inferred is reported with inferred: true and an undo_note; a creation's machine_id is preferred whenever there is one. - Wire shapes checked against feat-cmux-next 70a56a0 (#23155 merged): unchanged from the PR ref. Tests: the fake leaves out loaded as the app does today, forgets its answers on restart, and can drop or lose a create or answer no result. New cases show that a dropped answer plus a restart, and a null result, each end with one machine; that an unreached create waits on an empty list and is sent again with its key once the list is loaded; and that a plan refusal keeps the key until --forget. Docs match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d48-93a5-7bd1-9d6e-e007f1c85e28&host=cosmic-gray
|
Shipped in:
|
The base merge brought +1103 more App lines from 26 commits since fee17be; the cap is the count at the merged head, and the baseline names the largest (#23651 +305, #23155 +285, #23527 +132, #23324 +92, #23638 +82, #23492 +57, #23588 +53, #23109 +48). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bd2-60d2-70e1-8179-d3aa717c8828&host=cosmic-gray
…h (app size base red) (#23540) * cmux-next: raise the CmuxNextApp size cap to 134745, naming the growth (app size base red) check-app-size.sh is red on every feat-cmux-next push: "CmuxNextApp is capped at 128667 lines (now 134745, +6078)". The cap was set at 7817f10; since then 203 first-parent commits changed the target, net +6078. None of it is generated or dead (types checked for references across Packages/macOS); it is feature code, +4040 of it direct pushes (Files and Find +1795) and +2038 from PRs. The baseline comment names the largest. Follow-ups that would pay it back (SW14): FileExplorer/ (22 files, 1647 lines) into its own target with CmuxNextFiles.FolderWatcher instead of its own FSEvents stream, and BrowserDownloadsPopup.swift (468) out of the App target. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bd2-60d2-70e1-8179-d3aa717c8828&host=cosmic-gray * cmux-next: the App size cap is the count at ae0d9b7 (135848) The base merge brought +1103 more App lines from 26 commits since fee17be; the cap is the count at the merged head, and the baseline names the largest (#23651 +305, #23155 +285, #23527 +132, #23324 +92, #23638 +82, #23492 +57, #23588 +53, #23109 +48). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bd2-60d2-70e1-8179-d3aa717c8828&host=cosmic-gray --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
An agent's
cmux cloud kill-machine --confirm truewith no--targetused to kill whatever machine the person's window showed (or the only Cloud machine). Cloud machine actions run from the CLI, MCP, a script, a page or another client now act only on the machine they name, and they answer with their result instead of opening a sheet on the person's screen.What changes
CloudHandlers.machinefalls back to the active window's machine (then the only machine) only fororigin == .user. Any other run without--target machine:<id>is refused with the existingCloudStrings.noMachine("…pass --target machine:."). This covers every machine action: kill, pause, resume, resize, snapshot, restore, delete-snapshot, hand-off, exec, files, ports, and the rest. New Cloud Workspace keeps the fallback for every caller, because a missed machine there only opens a workspace.cloud new-machinereturns the machine. A CLI/MCP run answers withresult: {machine_id, title, status, target: "machine:<id>"}once the server returns the machine. A slow server answers{pending: true, creation_id}after 30 s, insideaction.run's 40 s result deadline, so a caller never reads a slow create as a failure and starts a second machine. A declined or dismissed create is refused.cloud execreturns output. For a non-person run on a machine that allows API exec, the command goes throughPOST /api/vm/{id}/exec(25 s cap) and returns{exit_code, stdout, stderr}. It opens no tab. A person's run still types the command into a new tab.machine-status,machine-ports,machine-tools,hand-off-machine,snapshot-machine,promote-machine-to-template,files-list,file-read,file-stat,prepare-scp,network-list,firewall-list|get,domain-listandpublication-listreturntextplus structured fields.new-terminal-on-machinereturnsworkspace_idand moves the window only when the run asks for focus. A person's run is unchanged. This follows the pattern cmux NEXT: Cloud Diagnostics shows one readable line; the JSON report is behind Copy details and returned to the CLI #23109 uses for diagnostics.action list/action describesay why an action is unavailable. An action whoserequirescontext is missing now carriesmissing_context,available_with_target(for Cloud machine actions,["machine"]) and a localizedcontext_reason("Needs cloudWorkspace in the focused window, or pass --target machine:.").The CLI already prints
action.run'sresult, so no Rust change is needed. Actions that now answer with data are markedwaitsForResult, which gives them the 40 s result deadline.Files #23109 edits were avoided: no hunk touches
cloudDiagnostics/diagnostics(),CloudPresenter.swift,AppControl+Cloud.swiftorCmuxNextDaemonService/CloudStrings.swift. The one change inCloudHandlers+Account.swiftis inbindCreation, away from the diagnostics region. New strings live in the app module'sCloud.xcstringsandCmuxNextControl/Localizable.xcstrings, with every locale.Changelog
--target machine:<id>and return their result (new machine id, command output, status) instead of opening a window.Tests
CloudCLITargetTests(CmuxNextAppTests): CLI/MCP/script/remote/page runs without a target are refused, a named target resolves, a missing one isnot_found, the person keeps the fallback, a run nested inside a CLI run is refused too, a CLI answer comes back as the run's result, slow work answerspendingwhen its wait ends and quick work answers with its result, a refusal in the work reaches the caller as one, and new-machine answers with the machine,pending, declined and failed.ActionContextGapTests(CmuxNextControlTests): the unavailable reason inaction.list/describe.Red/green on the fleet (
cmux-ci run --class light --script scripts/ci/run-swift-testing-suites.sh --arg Packages/macOS/CmuxNext --red):feat-cmux-nextplus only the target tests (scratch 80c30ad, step 0237514e).aRunThatIsNotThePersonsNeedsAMachineTargetfails for all 5 origins (cli, mcp, script, remote, page) with "an error was expected but none was thrown and CloudMachineSession was returned". That's the fallback bug.aNestedRunInsideACLIRunNeedsAMachineTarget: no error is thrown, so a nested run still gets the fallback.slowWorkAnswersPendingWhenItsWaitEnds: the answer took 8.53 s against a 5 s limit, with a 100 ms wait and 8 s of work. The race waited for the work.CloudCLITargetTestsandActionContextGapTestspass.VideoWallpaperTests, a CoreVideoCVPixelBufferPoolCreatePixelBuffersegfault inVideoJob.frame. That's code this PR doesn't touch.Index out of rangein PR CI'scmux-next swift test. This isAgentPaneImageActionsTests.showInFinderSelectsANamedCopyOfTheImage, not this PR:revealed().countis 4 where the test expects 5. The test then readsrevealed()[4]unchecked (AgentPaneImageActionsTests.swift:75), which crashes the whole single-process run.cmux-next swift test(itsif:excludes pushes tofeat-cmux-next), so base has no log of its own to compare.CmuxNextAppTestsdidn't compile until 0f6199c, so the branch now merges base.Observed / Not run
python3 scripts/cmux-next/prepush-gate.py: no new violations.pendingat 30 s. The oldwithTaskGrouprace awaited the work'sTask.value, which ignores cancellation, so it waited for the whole snapshot. Both sides now yield into oneAsyncStream(firstAnswer).CloudHandlers.machine,answer, exec and new-terminal readActionRunScope.current?.origin ?? invocation.origin, so a nested perform inside a CLI run gets no fallback.cloud execon a machine without API exec is refused withcloud.cli.execUnavailable, in every locale ofCloud.xcstrings. Before, it typed into a tab whose output it couldn't return.cmux-cloud-vmskill docs say--targetis required and that results come back../scripts/localize-changes --catalog …for both catalogs: the new keys are translated in every locale the catalog carries.localization_catalog.py checkshows the same base parity error count with and without this PR (41373 at this head).ControlRouter+ActionRun.swift(result) andcli/app.rs(prints the reply).Not in this PR (follow-ups)
cmux machine list/--catalog/machine <vm-id> showdon't list Cloud machines. These are daemon methods (resource_api.rsLocalResourceMachineService), not app methods. The app only feeds--catalogthroughMachineCatalogPublisher→MachineRegistry.catalogRecords(), which drops Cloud machines with nomachineResourceID, andMachineCatalogRecord.validIDaccepts onlymachine_<32hex>, nevervm-…. A full fix needs both sides: Rustmachine.rs/resource_api.rs/machine_catalog.rsacceptvm-ids, a compatibility field, and remote records without--catalog; SwiftMachineCatalogRecordgains those fields. The cheaper alternative is to have the Rust CLI merge the app'scloud.machinesrows intomachine listfor thecmuxsurface. Until then,cmux app call cloud.machineslists them.cloud execno longer needs it. In general, aterm_…on a Cloud daemon givesselector.not_found, because the CLI talks to one daemon socket, and<session>:term_…givessocket.unreachable, becausefederation.rssays Cloud and SSH sessions "have no CLI transport yet".PublicIDTargetResolver.publicIDalso returns bare ids increated. This needs an app-proxied terminal read or a CLI transport to remote sessions.cmux workspace new-on-machine --machine <id>fails with "requires --machine": the global-flag parser incli.rs(--machineroutes a daemon) takes--machinebefore the action sees it, except afteraction run. This needs a Rust fix that passes--machinethrough for CLI action verbs whose schema has amachineargument.failed.CloudMachineStageInput.stagemapspaused+ idle link to.failed(machinePaused). It should be its own stage. That touches the progress view and sidebar, so it's a separate PR.workspace new-on-machinestill returnscreated: []: its workspace is created on a remote daemon, andscope.createdonly resolves home-daemon objects to public ids.No capture: CLI, MCP and agent results only (JSON answers, refusals and
action describefields), so nothing on screen changes. A person's own runs still open the same sheets and tabs.🤖 Generated with Claude Code
Agent session: cosmic-gray