Skip to content

cmux NEXT: Cloud machine actions from the CLI need --target and return their result - #23155

Merged
teamleaderleo merged 8 commits into
feat-cmux-nextfrom
cloud-cli-targets
Oct 21, 2026
Merged

teamleaderleo merged 8 commits into
feat-cmux-nextfrom
cloud-cli-targets

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

An agent's cmux cloud kill-machine --confirm true with no --target used 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

  • No fallback machine for automation. CloudHandlers.machine falls back to the active window's machine (then the only machine) only for origin == .user. Any other run without --target machine:<id> is refused with the existing CloudStrings.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-machine returns the machine. A CLI/MCP run answers with result: {machine_id, title, status, target: "machine:<id>"} once the server returns the machine. A slow server answers {pending: true, creation_id} after 30 s, inside action.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 exec returns output. For a non-person run on a machine that allows API exec, the command goes through POST /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.
  • Other Cloud reads return data instead of a sheet when the run is not a person's: 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-list and publication-list return text plus structured fields. new-terminal-on-machine returns workspace_id and 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 describe say why an action is unavailable. An action whose requires context is missing now carries missing_context, available_with_target (for Cloud machine actions, ["machine"]) and a localized context_reason ("Needs cloudWorkspace in the focused window, or pass --target machine:.").

The CLI already prints action.run's result, so no Rust change is needed. Actions that now answer with data are marked waitsForResult, which gives them the 40 s result deadline.

Files #23109 edits were avoided: no hunk touches cloudDiagnostics/diagnostics(), CloudPresenter.swift, AppControl+Cloud.swift or CmuxNextDaemonService/CloudStrings.swift. The one change in CloudHandlers+Account.swift is in bindCreation, away from the diagnostics region. New strings live in the app module's Cloud.xcstrings and CmuxNextControl/Localizable.xcstrings, with every locale.

Changelog

  • Changed: Cloud machine actions run from the CLI or an agent need --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 is not_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 answers pending when 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 in action.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):

  • Red, first round: base feat-cmux-next plus only the target tests (scratch 80c30ad, step 0237514e). aRunThatIsNotThePersonsNeedsAMachineTarget fails 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.
  • Red, review fixes: 4d9d68f plus only the new tests (scratch 2ce8b4f, step a233d043). Two tests fail:
    • 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.
  • Green: head e7f28ea (step a9594174). CloudCLITargetTests and ActionContextGapTests pass.
  • Other suites fail in the same step on both sides: 35 fail on both the red and the green run.
    • Only on green: VideoWallpaperTests, a CoreVideo CVPixelBufferPoolCreatePixelBuffer segfault in VideoJob.frame. That's code this PR doesn't touch.
    • Only on red: three palette and space-switch timing suites.
  • Index out of range in PR CI's cmux-next swift test. This is AgentPaneImageActionsTests.showInFinderSelectsANamedCopyOfTheImage, not this PR:
    • revealed().count is 4 where the test expects 5. The test then reads revealed()[4] unchecked (AgentPaneImageActionsTests.swift:75), which crashes the whole single-process run.
    • The same crash happens on the base-plus-tests scratch run (step 0237514e, base 8697514). This PR doesn't touch that file or module.
    • Base push runs skip cmux-next swift test (its if: excludes pushes to feat-cmux-next), so base has no log of its own to compare.
  • An earlier red attempt on adfb21f didn't count: base CmuxNextAppTests didn't compile until 0f6199c, so the branch now merges base.

Observed / Not run

  • python3 scripts/cmux-next/prepush-gate.py: no new violations.
  • Review fixes since the independent review of 4d9d68f (NO-SHIP):
    • Snapshot and promote-template now answer pending at 30 s. The old withTaskGroup race awaited the work's Task.value, which ignores cancellation, so it waited for the whole snapshot. Both sides now yield into one AsyncStream (firstAnswer).
    • CloudHandlers.machine, answer, exec and new-terminal read ActionRunScope.current?.origin ?? invocation.origin, so a nested perform inside a CLI run gets no fallback.
    • A CLI or agent cloud exec on a machine without API exec is refused with cloud.cli.execUnavailable, in every locale of Cloud.xcstrings. Before, it typed into a tab whose output it couldn't return.
  • The cmux-cloud-vm skill docs say --target is 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 check shows the same base parity error count with and without this PR (41373 at this head).
  • Not run: no live app, CLI, socket or MCP calls (by rule). The CLI behavior is read from ControlRouter+ActionRun.swift (result) and cli/app.rs (prints the reply).

Not in this PR (follow-ups)

  1. cmux machine list / --catalog / machine <vm-id> show don't list Cloud machines. These are daemon methods (resource_api.rs LocalResourceMachineService), not app methods. The app only feeds --catalog through MachineCatalogPublisher → MachineRegistry.catalogRecords(), which drops Cloud machines with no machineResourceID, and MachineCatalogRecord.validID accepts only machine_<32hex>, never vm-…. A full fix needs both sides: Rust machine.rs/resource_api.rs/machine_catalog.rs accept vm- ids, a compatibility field, and remote records without --catalog; Swift MachineCatalogRecord gains those fields. The cheaper alternative is to have the Rust CLI merge the app's cloud.machines rows into machine list for the cmux surface. Until then, cmux app call cloud.machines lists them.
  2. Reading a Cloud machine's terminal from the CLI. cloud exec no longer needs it. In general, a term_… on a Cloud daemon gives selector.not_found, because the CLI talks to one daemon socket, and <session>:term_… gives socket.unreachable, because federation.rs says Cloud and SSH sessions "have no CLI transport yet". PublicIDTargetResolver.publicID also returns bare ids in created. This needs an app-proxied terminal read or a CLI transport to remote sessions.
  3. cmux workspace new-on-machine --machine <id> fails with "requires --machine": the global-flag parser in cli.rs (--machine routes a daemon) takes --machine before the action sees it, except after action run. This needs a Rust fix that passes --machine through for CLI action verbs whose schema has a machine argument.
  4. A paused machine's stage reads failed. CloudMachineStageInput.stage maps paused + idle link to .failed(machinePaused). It should be its own stage. That touches the progress view and sidebar, so it's a separate PR.
  5. workspace new-on-machine still returns created: []: its workspace is created on a remote daemon, and scope.created only resolves home-daemon objects to public ids.

No capture: CLI, MCP and agent results only (JSON answers, refusals and action describe fields), 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

teamleaderleo and others added 2 commits October 21, 2026 00:37
…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
@teamleaderleo teamleaderleo added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 21, 2026
@cursor

cursor Bot commented Oct 21, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot 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.

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review 76f0f18: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr23155 -f ref=cloud-cli-targets -f platform=macos -f backend_mode=local

Not verified: macOS jobs did not run: compile and app tests were skipped on 76f0f18a6b; ci-status is green without them.

CI passes on 76f0f18a6b (run 39619246667 attempt 1).
macOS jobs did not run: compile and app tests were skipped: Fast static checks skipped (the macos job needs both to succeed). Re-run CI once they pass, before merging.

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's; yours means the failing file is one this PR changes, also red on main that main's latest full suite fails the same way, seen on other PRs that it failed on another pull request's run lately.

web and size checks: green

Tour of 76f0f18a6b (run): no media. The tour job failed before the tour ran (build or setup); the run log has the output. Both apps run cmux-tui from e485f19 (tree 70ea17436d6a): the merge base's own tree 6a0ae02c7337 was not published yet.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Owned by projects-fa on cosmic-gray · session

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Owned by projects-fa on cosmic-gray · session

@blacksmith-sh

This comment has been minimized.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Bead cx-0k4u5 created for this PR by the cmux coordinator.

teamleaderleo and others added 2 commits October 21, 2026 01:02
…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
@blacksmith-sh

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
@blacksmith-sh

blacksmith-sh Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Found 2 test failures on Blacksmith runners:

Failures

Test View Logs
/tmp/tmpakg3chrr/summary-fail.json (live):/summary-fail.json (live): View Logs
/tmp/tmpp6_dfipm/summary-missing.json (live):/summary-missing.json (live): View Logs

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

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (independent subagent): SHIP at e7f28ea

Re-review of the fixes for the NO-SHIP at 4d9d68f:

  • Pending race: firstAnswer races through one AsyncStream, so a slow snapshot or promote answers pending at 30 s, before action.run's 40 s deadline. The defer cancels the timer and finishes the stream; nothing can resume twice, and the element type is Sendable. slowWorkAnswersPendingWhenItsWaitEnds would take about 8 s on the old code and fail its 5 s check.
  • Nested runs: reading ActionRunScope.current?.origin gives a nested CLI run the outer origin. A person's gesture binds a fresh scope, so it gets .user. This is the same pattern as CloseUndoToasts.isUserClose.
  • cloud exec without .exec: a non-person run is refused with cloud.cli.execUnavailable. That key exists in valid JSON with all 21 locales.

Not blocking: after pending, a late ActionFailure from a captured run is dropped without a log line. That was already the case before this PR.

The Index out of range crash in the single-process swift test comes from AgentPaneImageActionsTests.swift:75, an unchecked revealed()[4]. The file is unchanged since #20769, and fleet step 0237514e (base plus test files only) crashes the same way.

teamleaderleo and others added 3 commits October 21, 2026 05:39
…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
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

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 firstAnswer's stream to .makeStream(bufferingPolicy: .bufferingOldest(1)).

  • First answer still wins: the consumer gets the first value yielded whether both producers yield before iteration starts, one before and one after, or while it is already waiting.
  • The second value is harmless: it is either dropped or buffered after finish().
  • Compiles: the signature and element type are unchanged.

The commit clears the new unbounded-AsyncStream crash-safety and concurrency check, which flagged this line at 4437807.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

cmux-next guards / defaults-snapshot is red on 18 of 20 fresh feat-cmux-next PR heads in the last 2:00:00, so it is most likely a base red, not this PR's. The base-reds lane has it; a base merge picks up the fix once it lands.

@teamleaderleo
teamleaderleo merged commit 6082186 into feat-cmux-next Oct 21, 2026
@teamleaderleo
teamleaderleo deleted the cloud-cli-targets branch October 21, 2026 16:44
teamleaderleo added a commit that referenced this pull request Oct 21, 2026
…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
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

teamleaderleo added a commit that referenced this pull request Oct 21, 2026
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
teamleaderleo added a commit that referenced this pull request Oct 21, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant