Skip to content

carry of #26791: cloud.startChat first send - #28111

Merged
teamleaderleo merged 41 commits into
feat-cmux-nextfrom
lane/carry-26791-crash
Oct 31, 2026
Merged

teamleaderleo merged 41 commits into
feat-cmux-nextfrom
lane/carry-26791-crash

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

Carry of #26791: cloud.startChat makes a new chat's first send run in Cloud.

Original purpose: add cloud.startChat {task, cwd?, harness?, model?, key}. With it, a new chat's first send picks or makes a Cloud machine, copies the folder there, starts the chat on the machine and closes the local tab. The Sandboxed guards and the AgentPaneHandOff path stay as they are.

This carry merges feat-cmux-next, with hand-resolved conflicts in the Cloud move, account handlers, machine model, work-pick, setup wizard and related tests. It refreshes the crash-class selector comparison. #26791 remains the source PR; this PR supersedes it for landing.

Leo, 2026-10-30: "why is it >10 hours and it's not been progressed on? how it blocked #28111 for no reason?"

Fixes for the first send (Leo, 2026-10-30: "can you guys just do it", "like the initial 0 to 1 chat auto it sends to cloud")

  • Machine pick (hand-walk gap 4, carry of #26791: cloud.startChat first send #28111 (comment)). cloud.startChat no longer takes the team's first connected machine, which on Leo's account is often someone's busy lane VM. It now takes a machine this person created (CloudMachine.createdBy against the signed-in user) that is free: connected with no chat open, or paused by the idle timer. A machine named for chats (cloud-chats, cloud-chats-N) comes first. If none fits, it creates one named cloud-chats. Lane-pool VMs (fs-lane-N, lane-vm-N) are never picked. Move to Cloud's own pick is unchanged.
  • Retry pinned to the machine (cloud.startChat: pin a retry to the send's first machine (follow-up of #28111) #28845). See "A retry goes to the same machine" below.
  • First Claude call on a fresh VM (gap 5). acpmux resends a chat's first send once, 2 s later, if it failed with "OAuth token revoked" (hub/auto_resume/first_send.rs). The resend goes through the Retry button's path (retry_turn.rs resend_turn) under the chat's continuation mark. Stop, close, a move, or a person's prompt during the wait cancels it. It applies only to the chat's first turn and the prompt's own id, never to a ~resume- attempt. A resend that fails the same way shows the failure as before. daemon/cloud_handoff.rs holds that turn's failed report until the retry is decided. Expired or missing sign-ins and outages are unchanged.
  • Review BLOCK at 8b117be, all three findings fixed (router-review, gpt-6-astra):
    • [P1] The copy stays in scope. The copy uploads the Git working copy that holds the page's folder. The pane now checks that upload root against its roots too (AgentPaneHandOff) and hands it on as AgentPaneCloudStart.uploadRoot. The App copies only that folder (CloudStartChat.uploads), so a grant of /repo/paint never uploads /repo.
    • [P1] A retry never copies over the chat's edits. Destinations records, per send key and machine, that the send's copy finished. A retry whose copy finished opens its chat by its tab key without pushing again.
    • [P2] No second VM. A new machine for a send is created under AgentPaneCloudStart.createKey, derived from the send key, so a retry after a lost create answer gets the same machine back.
  • Not behind Labs (Leo, 2026-10-30, on cloud.startChat refusing with Labs off: "that seems dumb. i dont like putting so much stuff in labs"; and "WHAT GOOD REASON DO WE HAVE IN THE FIRST PLACE TO KEEP THIS STUFF IN FING LABS IF I FING ASKED FOR THESE F***ING FEATURES"). cloud.startChat no longer refuses with labs_off. It refuses only when Cloud is off by policy (cloud_disabled) or nobody is signed in (not_signed_in), and the person still picks Cloud for the send. The chat's machine size pick (CloudChatPick.read) no longer checks Labs for any caller. The other shipped Labs gates (Move to Cloud's menus, the setup wizard, Sandboxed runs) come off in their own PR.
  • Review: router-review (gpt-6-astra) SHIP at 8c9971b (earlier SHIP at 4337dc5, before the Labs change). Follow-up from 8c9971b: cloud.startChat: refuse an app-server Cloud link before creating a chat machine (follow-up of #28111) #28979 (refuse an app-server link before creating a machine). Earlier follow-ups: cloud.startChat: a lost success answer leaves the pane's hand-off hold, so a same-key retry can't recover the remote chat (follow-up of #28111) #28930 (a lost success answer keeps the pane's hold), cloud.startChat: pin a new machine's send before its create answers, so a lost create answer retries the same key (follow-up of #28111) #28931 (pin a new machine's send before its create answers), acpmux: a daemon restart during a first send's revoked-token retry wait drops the retry (follow-up of #28111) #28932 (a daemon restart during the 2 s first-send wait).
  • Not in this PR: fs-v1 on fresh VMs (cmux-tui: a fresh Cloud VM serves fs-v1 (Cloud first send, Move to Cloud) #28823) and the Codex 503 hang (separate lane).

Fixes since the carry (taken over from its unreachable owner)

  • The model reaches the machine (next-review P2). The remote seed carried only the prompt and the harness, so the model the page picked was dropped. AgentPaneCloudStart.seed(cwd:) now builds the seed with the model too. The request has no effort field, so there is no effort to forward.
  • Xcode 26.6 types the page's Cloud runner. AgentPageChats.seed writes the closure out with its type (as Composer Cloud: a chat starting in Cloud shows its progress under the message #26921 found it must), in place of the map form.
  • A retry opens the same tab (Codex review, P1). The machine's chat tab is created with the send's own key (AgentPaneCloudStart.tabKey). A retry after a lost answer gets that tab back, so the prompt is never sent to a second chat.
  • A retry goes to the same machine (Codex review, P2; cloud.startChat: pin a retry to the send's first machine (follow-up of #28111) #28845). CloudStartChat.Destinations (CloudStartChat+Machine.swift) pins each send key to the machine its first try took, keeping the newest 64 in the app's defaults. A retry names that machine, so its tab key gets the first try's tab back, never a second chat with the prompt on another machine. A pin to a machine that no longer exists is dropped.
  • The folder check runs off the main thread (@concurrent, FileManager.checked).
  • A second Cloud chat from the same folder keeps the first one's edits (Codex review, P1). Each send now copies the folder into its own place under the machine's work root (chats/<send key>, AgentPaneCloudStart.remotePlace), and a retry of the same send reuses that copy, so a second chat never writes over the first one's changes.

No capture: the method has no UI caller until the composer's Cloud row (#28686).

Risk

risk:high (a new host method that starts a remote machine and copies a folder to it). Not behind Labs. A signed-out or Cloud-disabled account refuses with its own code. The method has no UI caller until the composer's Cloud row (#28686), and only the person's Send gesture reaches it. A refusal releases the hold so the person can send again; a started chat keeps it.

The guardrails' app-size rule was skipped for the push, with this reason: CloudStartChat composes App-only services (AppServices, CloudMove, CloudHandlers, AgentTabStore), beside its siblings CloudMove and CloudStartSession. CmuxNextCloud can't see those services until the B4 host protocols land, and CloudMove.swift is at 366 of 400 lines.

Tests

  • AgentPaneCloudStartTests: the hand-off guards; the remote seed carries the prompt, agent and model; a retry's tab key matches its first try's, and another send's doesn't.
  • AgentPaneCloudStartTests: aFolderInsideAGitWorkingCopyIsCopiedOnlyWhenTheWholeCopyIsInScope, aRetryOfTheSameSendCreatesUnderTheSameKey.
  • CloudStartChatMachineTests (new): aFinishedCopyIsKeptPerSendAndMachineAcrossARelaunch, aPageNamedFolderIsCopiedOnlyAsItsCheckedUploadRoot, aNewMachineIsCreatedUnderTheSendsKey; and the pick (a teammate's machine is never taken, a busy machine is passed over for a free one, a machine named for chats comes first, lane-pool VMs are never picked, idle-paused is free and a person's pause is not, a machine with no creator counts only when an earlier send used it, new-machine naming); aRetryOfTheSameSendStaysOnItsFirstTrysMachine (cloud.startChat: pin a retry to the send's first machine (follow-up of #28111) #28845); a gone machine's pin is dropped; Destinations survive a relaunch and keep the newest 64.
  • acpmux: claude_auth::tests::a_revoked_token_is_told_from_other_sign_in_failures; hub::auto_resume::first_send::tests (a first send a revoked token failed goes again once and completes; a retried one that fails again is not retried; a later turn is not retried; a Stop or a prompt during the wait cancels the retry); cloud_handoff_tests::a_revoked_token_holds_the_turn_for_its_first_send_retry.

Observed / Not run

  • At caac4e4 the base (feat-cmux-next fe08891) is brought in again: the only conflict, the page's op list in AgentPageProvider.swift, keeps the base's acpmux.restartStuck and this PR's cloud.startChat.
  • Observed: the cmux-next prepush gate passes, apart from the app-size skip named above. The base merge's only conflict, the page's method list, keeps both sides.
  • Fleet runs at this head: app compile-check (cmux-ci) and the acpmux suites (153 lib tests, 59 hub_integration auto_resume/retry tests, clippy -D warnings clean; cmux-ci jobs 1db5ce0413f9b6d113726b0f, ba5f1a79ad4768e0ceaad195).
  • cmux-next checks: on the carry's head, its two failing steps were in files this PR doesn't touch. "No literal state roots" was acpmux help_examples/session.rs:179, and it passes on feat-cmux-next now. "String tables (21 languages)" is missing translations in the ActionCatalog, PaneCreation, Updater and CmuxHome tables.

Agent session: agent session

🤖 Generated with Claude Code

teamleaderleo and others added 7 commits October 28, 2026 00:23
…ze, task kind), sent as autosize

A chat sent to Cloud no longer needs a machine or size choice: CloudWorkPick reads the chat's
folder (repo key and GitHub repo from the remote, branch and whether it is pushed, git object
size, root files, task kind, .cmux/machine.toml, Codex broker) and Move to Cloud with no machine
to reuse, and New Cloud Machine from a chat, send its autosize with POST /api/vm. The answer's
sizePick stays on the machine's session for the hand-off. Behind Labs preview features.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6
…o from a local remote, no pick on the app-server link

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6
…Cloud with no machine or environment prompt

The page's Send with Cloud picked asks the host (`cloud.startChat {task, cwd, harness?, model?, key}`),
with Sandboxed's guards (a new chat, the Send's gesture, the replacements held, one at a time;
both now share AgentPaneHandOff). The App takes a Cloud machine as Move to Cloud does (a new one
sized by CloudChatPick), copies the folder with Move to Cloud's sync, opens the chat on the machine
with the prompt seeded as the person's, and closes this Mac's unstarted tab. Behind Labs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6
…t exist; the hand-off body is main-actor

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6
…he tab's daemon is local

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4ba8980ae4e8eecb82d6
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

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

Review 403c5ba: 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=pr28111 -f ref=lane/carry-26791-crash -f platform=macos -f backend_mode=local

web and size checks: green

Default changes

This pull request changes 1 default at 403c5ba2f8b9 (what everyone gets without configuring anything):

  • tunable-source.CmuxNextApp/Cloud/CloudStartChat+Machine.swift:isLanePool: (none) -> "9fb702a1033ea54c207c7602575d9ee465c1f68d50017641a75989516f217cb1"

Signed off on this commit by review (codex gpt-6-astra high, router-review).

Merge receipt: merged as 6a0ad1bf4da3 with these defaults changed, signed off by review (codex gpt-6-astra high, router-review).

Regenerate the snapshot with python3 scripts/ci/defaults_guard.py snapshot.

Shipped in:

@github-actions github-actions Bot added the risk:high Risk tier (docs/ci/risk-tiers.md) label Oct 30, 2026
@blacksmith-sh

blacksmith-sh Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Found 3 test failures on Blacksmith runners:

Failures

Test View Logs
test_a_picked_signing_label_has_a_rerun_alternative (main.RunsOnFallbackTests.test_
a_picked_signing_label_has_a_rerun_alternative)/
test_a_picked_signing_label_has_a_rerun_alternative (main.RunsOnFallbackTests.test_
a_picked_signing_label_has_a_rerun_alternative)
View Logs
test_every_listed_hook_is_one_glaeda_released_with_the_check (main.SigningHooksInSt
epWithGlaedaTests.test_every_listed_hook_is_one_glaeda_released_with_the_check)/
test_every_listed_hook_is_one_glaeda_released_with_the_check (main.SigningHooksInSt
epWithGlaedaTests.test_every_listed_hook_is_one_glaeda_released_with_the_check)
View Logs
test_no_runs_on_chain_ends_on_the_reserved_side_label (main.RunsOnFallbackTests.tes
t_no_runs_on_chain_ends_on_the_reserved_side_label)/
test_no_runs_on_chain_ends_on_the_reserved_side_label (main.RunsOnFallbackTests.tes
t_no_runs_on_chain_ends_on_the_reserved_side_label)
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 (codex gpt-6-astra high, next-review): BLOCK at acbeafe

  • [P2] CloudStartChat.swift:79 — Requested model is discarded. cloud.startChat accepts and validates model, but the remote chat seed forwards only the harness and prompt. A request selecting a specific model therefore runs with the remote default instead. Forward the selected model before sending the first prompt; the existing tests only verify parsing.

@teamleaderleo

teamleaderleo commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator Author

Owned by an agent session · session

teamleaderleo and others added 2 commits October 31, 2026 00:03
…the seed's Cloud runner for Xcode 26.6

next-review's P2: CloudStartChat built the remote seed with the harness and prompt only,
so the model the page picked was dropped. AgentPaneCloudStart.seed(cwd:) now builds it
with the model too, with a test. The request carries no effort, so there is none to forward.

AgentPageChats.seed's Cloud runner is written out with its type, as #26921 does: #26921
found Xcode 26.6 could not type the map form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-22973a84aab760788504
…r check reads off the main thread

next-review's BLOCK at b3389d1 (Codex gpt-6-astra): CloudStartChat opened the machine's
chat tab with a fresh creation key, so a retry of the same send after a lost answer could
make a second chat and send the prompt twice. The tab's key is now the send's own
(AgentPaneCloudStart.tabKey), with a test that a retry's key matches and another send's
does not.

The folder check (FileManager.fileExists) ran on the main actor; it now runs in a
@Concurrent helper (guardrails main-blocking).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-22973a84aab760788504
@cursor

cursor Bot commented Oct 31, 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.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6-astra, router-review): BLOCK at f832258

BLOCK at f832258

Visual verdict: no images supplied (range review); the visual verdict must come separately

  1. [P1] [blocking] Starting another Cloud chat can overwrite existing Cloud work. CloudStartChat.swift:82 unconditionally pushes into a directory determined solely by the local folder. Another chat from that folder reuses the machine and destination. The sync implementation overwrites remotely modified files with local contents, so edits from the earlier Cloud chat are lost. Use an isolated destination per send, or preserve remote changes through conflict checking.

  2. [P2] [follow-up] Retries are not pinned to the original machine. CloudStartChat.swift:76 selects a machine anew on every attempt. If machine A creates the tab but disconnects before returning its answer, a retry can select connected machine B. The same tabKey then creates another tab because deduplication is local to each daemon. Retain the selected machine for each send key and retry there.

Static review completed; Swift tests could not run because this Linux box has no Swift toolchain.

Follow-ups:

  • Finding 2: Pin retries to the original Cloud machine.

exit=1

…t named; guardrail exception dates are real dates

Router review at c1d0cf8 (blocking): after a team switch the store keeps the
previous team's sessions while an omission waits on a newer read, so the pick
could take one of them. CloudService.listedMachineIDs holds the machines the
loaded list named (and ones made since); candidates come only from it, and a
pin stands while listed, or while only the store has it. Test:
aPinStandsOnlyOnThisAccountsList. Review P2: exception dates parse as calendar
dates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3bab30503f0b9a7059e5
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6.1-sol xhigh, router-review): SHIP at 52d4e01

Reviewed the range from the merge base to 52d4e01. No blocking findings.

Follow-ups (owned by this PR's lane, to be filed as issues linking this receipt):

  • [P1] [follow-up] A create that reaches Cloud but loses its response is not reliably idempotent. The destination is recorded only after createMachine returns, so a retry can pick another free machine and skip the same-key create; the first VM is orphaned. Retries also lack a CloudMachineCreation, so the local create gate is not bypassed for an already-sent key. Fix: record or recover the destination before an uncertain create retries, and bypass the gate for a sent key. Also listed on the Rust move, cloud.startChat: machine pick, per-send ledger, keys and the copy move to cmux-tui (Rust) #29046.
  • [P2] [follow-up] guardrails.excepted iterates exceptions without checking it is a list, so {"exceptions": null} raises. Treat a non-list as invalid and leave refusals unchanged.

…26791-crash

# Conflicts:
#	plans/cmux-next/arch-ratchet.json
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6-astra high, router-review): SHIP at 403c5ba

Reviewed the range 1fd321e..403c5ba, the base merge of the head that got SHIP at 52d4e01. No blocking findings.

Follow-ups:

This branch was previously deployed

3 inactive deployments
artifacts — 403c5ba2 Deployed Oct 31, 2026 by teamleaderleo via publish tree-addressed cmux-tui #88702
sentry-dsyms — 403c5ba2 Deployed Oct 31, 2026 by teamleaderleo via upload cmux-tui dSYMs to Sentry #88702
ci-routing-agent — 403c5ba2 Deployed Oct 31, 2026 by teamleaderleo via cmux-tui tree preflight #88702
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dev-build Build a fleet dogfood build of each push (newest head under load) risk:high Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant