Skip to content

cloud.startChat: machine pick, per-send ledger, keys and the copy move to cmux-tui (Rust) - #29046

Closed
teamleaderleo wants to merge 22 commits into
feat-cmux-nextfrom
lane/cloud-start-chat-rust
Closed

teamleaderleo wants to merge 22 commits into
feat-cmux-nextfrom
lane/cloud-start-chat-rust

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator

HOLD: stacked on #28111; do not merge into its branch. Retarget to feat-cmux-next and merge base after #28111 lands, then rerun CI (the Swift compile has not run at this base).

Leo, 2026-10-30: "AS MUCH RUST AS POSSIBLE INSTEAD OF SWIFT" and "SPEED UP THESE FUCKING ITERATIONS". Rule: leo-workspace machines/claude-rulebook.md, "New app logic goes in Rust".

Stacked on #28111 (branched from its head 8c9971bb5cac; base is its branch, so this diff is only the move). Once #28111 lands, this retargets to feat-cmux-next. Don't land before #28111.

What moves

A new cmux link start-chat CLI in cmux-tui (cmux-tui/crates/cmux-tui/src/link/start_chat_cli*). Swift reaches it the same way it reaches link sync push: through CloudMoveCLI, one JSON line on stdout.

  • plan --request JSON: picks the machine for a first send. That is this person's free machine or one named cloud-chats, never a teammate's machine or a lane-pool VM; otherwise a new machine named cloud-chats[-N]. A retry stays on the machine its first try pinned. Machines are pinned before anything runs on them. The answer carries the send's create, tab and place keys.
  • pin --key --machine: pins a machine the App just created.
  • copy --key --machine --socket --local --subpath --work-root...: pushes the folder to the send's own chats/<key>/<name-hash> place. It falls through work roots on fs.permission_denied. If this send's copy already finished on that machine, it never pushes again.
  • The per-send ledger moved from UserDefaults to <state>/cloud-start-chat/destinations.json (0600). Reads and writes happen under a file lock, so two sends at once don't drop each other's pins.

Swift keeps four jobs: it lists its machines for the plan, creates the machine the plan asks for, connects and opens the seeded chat with the plan's tab key, and closes the local tab. The place: parameter #28111 added to CloudMove.sync is gone, and CloudMove.failure(syncing:) is shared between Move to Cloud and Start Chat.

Line counts (vs #28111's head)

added removed net
Swift sources 122 235 -113
Swift tests 18 183 -165 (ported to Rust)
Rust (cmux-tui) 1,038 1 +1,037 (652 logic, 386 tests)

The Rust logic is longer than the Swift it replaces for three reasons: the CLI layer, the arg parsing, and the locked file ledger.

Not moved, on purpose

The pane's folder scope check (AgentPaneHandOff.folderInScope) stays in Swift for now. It is built on AcpmuxPathPolicy, which uses macOS getattrlist canonical spelling and is shared with every pane path check. Moving it means moving that whole policy, which is its own PR.

Tests

  • Rust, on the Linux pool (cmux-ci test --setup rust): cargo test -p cmux-tui --lib link::start_chat_cli passed 17/17, plus fmt and clippy clean on the new files. They cover every case from the removed CloudStartChatMachineTests and the key tests in AgentPaneCloudStartTests, plus new ones: the work-root fallthrough, no second copy on retry, the ledger's 0600 mode and 64-entry cap across a relaunch, and refusal of escaping paths.
  • Swift: the fleet compile-check gave no verdict (controller TimeoutError), so this PR's CI compile is the Swift check.

Risk

A behavior-preserving move of #28111's logic. The one behavior change is that pins live in the state folder, not UserDefaults. #28111 hasn't shipped, so there is no data to migrate.

Other Cloud Swift logic to move (ranked)

  1. The Move/Bring Back retry state machine and ledgers (CloudMoveRetries, CloudMovedChats, control flow in CloudMove/CloudBringBack, ~450 lines) into link/agent_cli + sync_cli.
  2. Machine update outcome mapping and the legacy path (CloudMachineUpdateTarget/Updates/LegacyMachineUpdate, ~350 lines) into link/machine_update.
  3. The work pick and repo-key parsing (CloudWorkPick, ~160 lines) into cmux-host/cloud.
  4. GitHub setup command building and parsing for the setup wizard (CloudGitHubSetup, ~220 lines) into cmux-host/cloud/github.
  5. The machine secrets plan and ledger (CloudMachineSecrets, ~300 lines) into link/access_cli; the Keychain read stays in Swift.
  6. Access asks polling and upload staging (CloudAccessAsks, CloudUploads) via a streaming link access watch.
  7. The Codex broker refresh scheduler, into acpmux.
  8. CloudService itself is mostly wiring and observable state, so it stays thin Swift.

🤖 Generated with Claude Code

Agent session: agent session


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


Summary by cubic

Moves cloud.startChat's machine pick, per-send pin ledger, send keys, and folder copy out of Swift into a new cmux link start-chat CLI in cmux-tui. Swift now only lists machines, creates the machine the plan asks for, opens the seeded chat, and closes the local tab.

  • The ledger moved from UserDefaults to <state>/cloud-start-chat/destinations.json (0600), read and written under a file lock so concurrent sends don't drop each other's pins.
  • A retry of a send stays on its first try's machine, and a send whose copy already finished never copies again.
  • Removed CloudMove.sync's place: parameter; CloudMove.failure(syncing:) is shared between Move to Cloud and Start Chat.
  • Swift pick and key tests are ported to Rust (link/start_chat_cli/tests.rs), which also covers the work-root fallthrough, the ledger's 0600 mode and 64-entry cap, and escaping-path refusal.

This is a behavior-preserving move except for the ledger's location, and since #28111 hasn't shipped there is no data to migrate. The pane's folder-scope check stays in Swift for now because it depends on AcpmuxPathPolicy.

Written for commit a45a84b. Summary will update on new commits.

View guided diff Turn on auto-fix

teamleaderleo and others added 22 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
…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
… its own copy on the machine

Two blocking findings from the Codex review of #28686, both in this PR's code:

- The page named the folder whose tree is copied to Cloud, and the host only checked that it was
  absolute. A page script could name any folder on this Mac. The hand-off now takes the folder's
  canonical path (realpath, off the main actor) only when it is inside one of the pane's roots by
  components, the same roots AcpmuxPathPolicy uses for a chat that starts here: the pane's own and
  the New Tab scan's, never home or above it, plus the folders the person added. Anything else is
  refused with transport.path_outside_roots before the hold and the gesture. No folder from the
  page still means the tab's own folder, which the host reads.
- Every Cloud start from the same folder synced into the same folder on a reused machine, and
  Move to Cloud's sync overwrites every file the machine changed, so a second Cloud chat could
  write over the first one's work. A start now copies into its own place under the work root
  (chats/<send key>). A retry of the same send lands in the same copy.

Tests: a folder outside the roots, a link out of a root, a `..` path out of it, a missing
folder, the disk's root, home, and a folder under home when the pane's folder is home are all
refused and never reach the runner. The root's own folder still starts with its canonical path,
and a send's place is its own and stable for a retry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-672a743921bc7917cca7
AgentPageProvider.swift: keep the base's acpmux.restartStuck and this PR's cloud.startChat op.

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

Leo (2026-10-30): "like the initial 0 to 1 chat auto it sends to cloud".

- The machine: never the team's first connected one (often someone's busy lane VM). A machine
  this person created that is free (no chat open, or idle-paused) or named for chats
  (cloud-chats[-N]), else a new one named cloud-chats. Lane-pool VMs (fs-lane-N, lane-vm-N)
  are never picked.
- #28845: CloudStartChat.Destinations pins each send key to the machine its first try took
  (newest 64, kept in the app's defaults), so a retry names that machine and its tab key gets
  the first try's tab back instead of a second chat with the prompt on another machine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3bab30503f0b9a7059e5
…es again once, 2 s later

Leo (2026-10-30): "like the initial 0 to 1 chat auto it sends to cloud". On a fresh Cloud VM
the first Claude call failed once with "Failed to authenticate: OAuth token revoked" and the same
call 2 s later ran (cmux#28111 hand-walk, gap 5).

- claude_auth::is_revoked_token: only a revoked token, never expired/missing sign-in or an outage.
- hub/auto_resume/first_send.rs: on the chat's first turn only, and only for a prompt of its own
  (never a ~resume- attempt), the turn is resent once as P~resume-1 after 2 s through the Retry
  button's resend (retry_turn.rs resend_turn), under the chat's continuation mark, so Stop,
  close, a move or a person's prompt during the wait keep it from going. A retry that fails the
  same way is shown as before. Decisions are logged (first_send_retry, auto_resume_skipped).
- daemon/cloud_handoff.rs: such a turn is not reported failed before its retry is decided.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3bab30503f0b9a7059e5
…inished send again, create under the send's key

router-review BLOCK at 8b117be (gpt-6-astra), all three findings:

- [P1] 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), hands it on as
  AgentPaneCloudStart.uploadRoot, and the App copies only that folder (CloudStartChat.uploads):
  a grant of /repo/paint never uploads /repo.
- [P1] 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, which would overwrite
  what the chat already changed there.
- [P2] A new machine for a send is created under AgentPaneCloudStart.createKey (from the send
  key), so a retry after a lost create answer gets the same machine, never a second VM.

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

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

Leo (2026-10-30), on cloud.startChat refusing with Labs off: "that seems dumb. i dont like
putting so much stuff in labs". The labs_off refusal goes (cloud_disabled and not_signed_in
stay), and the send's create reads the chat's size pick without Labs. Move to Cloud and the
setup wizard keep their Labs-gated pick (requiresLabs defaults to true).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3bab30503f0b9a7059e5
Leo (2026-10-30): "WHAT GOOD REASON DO WE HAVE IN THE FIRST PLACE TO KEEP THIS SHIT IN FUCKING
LABS IF I FUCKING ASKED FOR THESE FUCKING FEATURES". CloudChatPick.read no longer checks Labs,
so drop the requiresLabs parameter added in 4c71cc8: every caller (cloud.startChat, Move to
Cloud, the setup wizard) sizes the chat's machine.

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

Leo, 2026-10-30: "AS MUCH RUST AS POSSIBLE INSTEAD OF SWIFT" and "SPEED UP THESE
FUCKING ITERATIONS". `cmux link start-chat plan|pin|copy` owns which machine a
first send takes, the per-send ledger (now in the state folder, locked), the
create/tab/place keys and the copy that never runs twice; the App lists its
machines, creates the one asked for, opens the chat and closes its tab.

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

Owned by an agent session · session

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex, router-review): SHIP at a45a84b

SHIP at a45a84b

Visual verdict: no images; Leo tries the build

No actionable correctness findings. Reviewed the Swift integration, CLI dispatch, machine selection, ledger locking, and copy retry behavior.

Builds and tests were not rerun: Cargo and Swift are unavailable in this box.

Follow-ups: none

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

For the cloud.startChat Rust move: the account and scope rules #28111 patched in Swift, each fixed after a router review. Port them to Rust with one test each, and delete the Swift.

Account and team

  • Pin a send's machine under its account (pinKey(key, account:), keyed on user and team). Key the copy record (place, copiedChat) the same way, so a send's pin and copy record are one ledger entry and are evicted together.
  • Before picking: refresh until the machine list is the current account's (hasLoadedCurrentMachines). Take candidates only from the machines that list named, plus ones created since (listedMachineIDs). Never take sessions the store keeps for a previous team while an omission waits on a newer read.
  • A machine the server gives no creator counts as the person's own only through this account's earlier sends (Destinations.machines(of:)), never another user's or another team's on this Mac.
  • Create under the send's team on every try (CloudMachineRequest.team, CloudService.createTeam), with the send's idempotency key.
  • Check the account again when the create returns (before the machine is added to the list), before the copy, and after the copy. A change means nothing opens and this Mac's tab stays.
  • Known gap: a copy already running finishes onto the old team's machine; it isn't cancelled midway.

Upload scope

  • A page-named cwd must be in the pane's roots, by canonical path. Never the New Tab page's scan roots (gestureRoots).
  • The upload root (the enclosing Git working copy) must be in scope too.
  • The working copy's git folders (the .git file's gitdir: and its commondir) must be in scope too (AgentPaneCloudStart.gitFolders).
  • With no cwd (the tab's own folder): only the folder itself, never a working copy above it, never home or above it (raw and canonical), and every git folder inside that folder (gitFoldersAreInside).
  • No upload for an app-server link session.

Retry

Copy link
Copy Markdown
Collaborator Author

SW-00 source review — two replay hazards in the Rust start-chat ledger (head a45a84b0fc3675cbdcf11e2a7aef89f0d70e6f0f)

I read the actual start_chat_cli/{ledger.rs,pick.rs}, start_chat_cli.rs, the Swift caller, and the new tests at this head. These findings are additional to the account/team and upload-scope port checklist, which is already tracked here. This stacked PR is correctly marked HOLD; these are repair/test inputs for its eventual retarget, not an invitation to land it now.

1. An unreadable ledger silently becomes an empty ledger, then a later write erases the recovery information. ledger.rs Ledger::read uses fs::read(...).ok().and_then(decode).unwrap_or_default(). Ledger::update subsequently writes that empty state after any mutation. A truncated file, transient read error, or newer-schema file therefore looks like “no pin/copy”; plan can choose another machine and copy can upload again. The code explicitly treats a lost pin as permission to repick. For a billable VM and a working copy that has been edited remotely, this is an unsafe recovery default. Change: propagate read/decode errors as ledger_failed, preserve the original bytes (or quarantine them before any repair), and require reconciliation against the authoritative machine/tab before repicking. Red test: pin + copiedChat; corrupt/truncate the JSON; retry the key; assert refusal/no new pick, bytes preserved, and no push. Add recovery test from known-good state.

2. A copy that commits remotely but loses its response is indistinguishable from a copy that never happened. start_chat_cli.rs, StartCopy::run, calls push before record_copy. If the remote sync completes and the transport returns an error/timeout, the ledger has no copied_chat. Retrying the key performs another sync push to the same folder; the PR itself documents that a second push overwrites remote edits. The existing test checks retry after a successful answer, which misses this boundary. Change: give the copy an immutable send/machine/destination identity and a remote commit receipt (or an equivalent read-before-write reconciliation). Keep copying/outcome_unknown durable until the receipt establishes completion; never blindly replay over a possibly live chat. Red test: fake push commits remote files then returns transport timeout; mutate remote files as the agent might; retry the same key; verify exactly one copy and untouched remote edits. Include quit/relaunch between attempts.

A related stress test: two CLI invocations with the same key cross the ledger-read → unlocked push interval. Ledger::update locks only short reads/writes and the comment relies on one-start-per-page in Swift; the CLI is independently callable. Prove the same-key concurrent callers single-flight or observe one authoritative receipt, with no second write.

Acceptance: every accepted send key has a stable recoverable destination; an unknown create/copy/tab result causes reconciliation, not fresh side effects. Keep #28111's team+scope guards during retarget, along with its tests. The full SW-00 investigation/owner-map is being written to HQ #5599.

Evidence: exact-head source inspection. I did not run Rust/Swift tests, a VM, or a fault-injection harness in this session; the suggested tests are the falsification gate.

Base automatically changed from lane/carry-26791-crash to feat-cmux-next October 31, 2026 09:27
@github-actions github-actions Bot added the risk:high Risk tier (docs/ci/risk-tiers.md) label Oct 31, 2026

Copy link
Copy Markdown
Collaborator Author

SW-05 follow-up: status-invalid machine selection in the held Rust first-send picker (head `a45a84b0fc3675cbdcf11e2a7aef89f0d70e6f0f`)

Independent review beyond the existing account/team port checklist and SW-00 copy/ledger replay review. This is source-only and this stacked PR correctly remains HOLD.

P2 — A stale connected link bypasses a terminal machine status when picking a first-send destination. In `pick.rs:82–107`, the admission predicate is `(machine.connected || machine.is_live() || machine.is_idle_paused()) && (marked(machine) || free(machine))`. A `cloud-chats` machine with authoritative `status: "deleting"` or `"failed"` and a still-true `connected` flag passes; even an ordinary named machine passes if `chats: Some(0)`. During async delete/failure or stale connection reads, the first-send flow can select a machine about to vanish, potentially starting a chat against a doomed VM. `Candidate::is_live` exists but cannot veto terminal status here.

Exact red cases: for each `status in ["deleting", "failed"]`, make a candidate owned by the current user with `connected=true, chats=Some(0)`, once titled `cloud-chats`, once `spare`; expect `pick(...)=None`, never that ID. Keep an idle-paused machine eligible and preserve a previously pinned legitimate offline machine for same-send recovery. Also distinguish a server state of `paused` due to a person's explicit pause from a lingering connected link; the current predicate selects that case, too.

Related P2 — New-machine naming ignores the slug used by the existing-machine picker. `start_chat_cli.rs:169` passes only `machine.title` to `new_machine_name`, while `Candidate::names` and `pick` examine both `title` and `slug`. With an existing machine titled “Personal” but slugged `cloud-chats`, a new machine is proposed with the already-taken `cloud-chats` name. Include both title and slug in the reserved-name set; test mixed-case slugs and names.

Minimal intervention: filter terminal/explicitly-paused statuses before any connected/marked preference, and feed all known machine names (including slugs) to the name allocator. These changes are local to the owned picker and can land with its existing Rust test suite after the held parent has passed. This review makes no claim about deployed Cloud traffic or executed tests.

SW-05: https://github.com/manaflow-ai/cmuxterm-hq/issues/5599#issuecomment-6541485560

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Awareness note from an agent lane that does not own this PR; it is not a review or a request to take the PR over.


Base merge of this PR is no longer mechanical. #29046 branched from #28111 at 8c9971b. #28111 then landed at 403c5ba, with 15 more cloud.startChat commits (in 8c9971b..403c5ba), and these live in the Swift Destinations/pick that this PR deletes:

  • 2208632, 933c7d8, 52d4e01: pins scoped to the account (pinKey(_:account:)); checkAccount, which takes only machines from the current account's list (listedMachineIDs, hasLoadedCurrentMachines); and the create made in the send's team.
  • 17731ac: a machine with no creator counts as yours only through this account's sends (machines(of:)).
  • 3b0af5d: each send gets its own copy folder (place(for:on:)), so a retry whose record is gone never copies over a started chat.
  • 10fb9a8, d76a98d, 6beb0f2, c1d0cf8: git-folder and home-folder checks (these are in CloudStartChat.swift, not the ledger).

Merging feat-cmux-next means porting the first three groups into start_chat_cli (ledger keys per account, place, the account check, and team on the create) and keeping the folder checks. Taking either side as-is drops them, and the first two groups decide which team's machine a folder is copied to. #29532 is stacked on this PR, with HOLD. It merges this branch once that is done.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Awareness note from an agent lane that does not own this PR; it is not a review or a request to take the PR over.


Carried onto feat-cmux-next in #29582, on its own branch: pr-owner claim refused this PR because its owner session still reads as alive, and nothing was pushed here. #29582 ports #28111's account, team and copy-folder rules into the Rust (the checklist above) and fixes both SW-00 replay hazards. SW-05 stays a follow-up. Once #29582 lands, this PR is superseded; its owner closes it.

teamleaderleo added a commit that referenced this pull request Oct 31, 2026
… per workspace

The workspace bindings are rebuilt on the carried cmux-tui start-chat:
each binding is kept under the account its send began in, and a
workspace's machine is reused only while it is usable (not failed,
deleting, deleted or destroyed, even when still connected).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Oct 31, 2026
…one machine per workspace

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Oct 31, 2026
…ui, with #28111's account rules (#29582)

* cloud.startChat: the machine pick, the send's pin and keys and the copy move to cmux-tui

Leo, 2026-10-30: "AS MUCH RUST AS POSSIBLE INSTEAD OF SWIFT" and "SPEED UP THESE
FUCKING ITERATIONS". `cmux link start-chat plan|pin|copy` owns which machine a
first send takes, the per-send ledger (now in the state folder, locked), the
create/tab/place keys and the copy that never runs twice; the App lists its
machines, creates the one asked for, opens the chat and closes its tab.

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

* start-chat: Flags owns its lifetimes; rustfmt

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

* start-chat: clippy

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

* cloud.startChat: carry #29046 onto base with #28111's account and copy rules in Rust

#29046 moved the machine pick, the send's pin and keys and the copy to
cmux-tui, from a #28111 head that predates 15 of #28111's commits. This
brings those rules into the Rust instead of dropping them:

- Every send is kept under the account it began in (user and team): its
  pin, its copy and what makes a creatorless machine the person's own.
- The App takes machines only from the list the server answered for that
  account, keeps a pin to a machine made since (`stored`), creates in the
  send's team and rechecks the account around the create and the copy.
- Each copy goes to a new folder of the send's own, kept before the push:
  a retry whose record was evicted, one whose answer was lost, or a second
  copy at once never writes over another (SW-00 #2).
- A ledger that cannot be read refuses instead of reading as empty, and
  its bytes are moved aside as they are (SW-00 #1).

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

* start-chat: the account test expects the one machine team A's sends ran on

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

* cloud.startChat: the sends the App kept in its user defaults carry over to cmux-tui once

Router review at 83b8b35 (blocking): moving the ledger to cmux-tui dropped
the App's own pins on upgrade, so a send in flight could open its prompt on
another machine and a creatorless machine stopped counting as the person's.
The App hands its entries to `cmux link start-chat import` before the first
plan and forgets them once taken in; cmux-tui keeps its own entry for a send
it already has and leaves out sends kept under no account.

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

* start-chat: the place test moves its copy instead of cloning it (clippy redundant_clone)

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

* start-chat: the import test borrows its keys instead of cloning them (clippy redundant_clone)

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

* ci: re-run the description checks on the edited body (no change)

The perf-section and risk checks read the description from the event
that started them; an edit through the broker started no new run.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-bot[bot] <348531757+cmux-bot[bot]@users.noreply.github.com>
teamleaderleo added a commit that referenced this pull request Nov 1, 2026
…orkspaces reuse and share machines (#29532)

* cmux NEXT Cloud: pick a chat's machine from its folder (repo, ref, size, 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

* cmux NEXT Cloud pick: clone the upstream's branch name, no GitHub repo 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

* cmux NEXT Cloud: cloud.startChat, a new chat's first send started in 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

* cloud.startChat: the page's folder is optional; the tab's own folder otherwise

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

* cloud.startChat: a started chat keeps the pane's hold; the folder must 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

* cloud.startChat: only a chat on this Mac starts one; the App checks the 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

* cloud.startChat: the machine's chat starts on the picked model; type 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

* cloud.startChat: a retry opens the same tab on the machine; the folder 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

* cloud.startChat: copy only a folder the pane may name, each send into its own copy on the machine

Two blocking findings from the Codex review of #28686, both in this PR's code:

- The page named the folder whose tree is copied to Cloud, and the host only checked that it was
  absolute. A page script could name any folder on this Mac. The hand-off now takes the folder's
  canonical path (realpath, off the main actor) only when it is inside one of the pane's roots by
  components, the same roots AcpmuxPathPolicy uses for a chat that starts here: the pane's own and
  the New Tab scan's, never home or above it, plus the folders the person added. Anything else is
  refused with transport.path_outside_roots before the hold and the gesture. No folder from the
  page still means the tab's own folder, which the host reads.
- Every Cloud start from the same folder synced into the same folder on a reused machine, and
  Move to Cloud's sync overwrites every file the machine changed, so a second Cloud chat could
  write over the first one's work. A start now copies into its own place under the work root
  (chats/<send key>). A retry of the same send lands in the same copy.

Tests: a folder outside the roots, a link out of a root, a `..` path out of it, a missing
folder, the disk's root, home, and a folder under home when the pane's folder is home are all
refused and never reach the runner. The root's own folder still starts with its canonical path,
and a send's place is its own and stable for a retry.

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

* fix(cloud.startChat): a first send takes this person's free machine, and its retry stays there

Leo (2026-10-30): "like the initial 0 to 1 chat auto it sends to cloud".

- The machine: never the team's first connected one (often someone's busy lane VM). A machine
  this person created that is free (no chat open, or idle-paused) or named for chats
  (cloud-chats[-N]), else a new one named cloud-chats. Lane-pool VMs (fs-lane-N, lane-vm-N)
  are never picked.
- #28845: CloudStartChat.Destinations pins each send key to the machine its first try took
  (newest 64, kept in the app's defaults), so a retry names that machine and its tab key gets
  the first try's tab back instead of a second chat with the prompt on another machine.

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

* fix(acpmux): a chat's first send that a revoked OAuth token failed goes again once, 2 s later

Leo (2026-10-30): "like the initial 0 to 1 chat auto it sends to cloud". On a fresh Cloud VM
the first Claude call failed once with "Failed to authenticate: OAuth token revoked" and the same
call 2 s later ran (cmux#28111 hand-walk, gap 5).

- claude_auth::is_revoked_token: only a revoked token, never expired/missing sign-in or an outage.
- hub/auto_resume/first_send.rs: on the chat's first turn only, and only for a prompt of its own
  (never a ~resume- attempt), the turn is resent once as P~resume-1 after 2 s through the Retry
  button's resend (retry_turn.rs resend_turn), under the chat's continuation mark, so Stop,
  close, a move or a person's prompt during the wait keep it from going. A retry that fails the
  same way is shown as before. Decisions are logged (first_send_retry, auto_resume_skipped).
- daemon/cloud_handoff.rs: such a turn is not reported failed before its retry is decided.

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

* fix(cloud.startChat): copy only a checked upload root, never copy a finished send again, create under the send's key

router-review BLOCK at 8b117be (gpt-6-astra), all three findings:

- [P1] 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), hands it on as
  AgentPaneCloudStart.uploadRoot, and the App copies only that folder (CloudStartChat.uploads):
  a grant of /repo/paint never uploads /repo.
- [P1] 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, which would overwrite
  what the chat already changed there.
- [P2] A new machine for a send is created under AgentPaneCloudStart.createKey (from the send
  key), so a retry after a lost create answer gets the same machine, never a second VM.

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

* fix(cloud.startChat): no await inside the upload root check's autoclosure (fleet compile)

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

* fix(cloud.startChat): not behind Labs; its machine is sized for the chat without Labs

Leo (2026-10-30), on cloud.startChat refusing with Labs off: "that seems dumb. i dont like
putting so much stuff in labs". The labs_off refusal goes (cloud_disabled and not_signed_in
stay), and the send's create reads the chat's size pick without Labs. Move to Cloud and the
setup wizard keep their Labs-gated pick (requiresLabs defaults to true).

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

* fix(cloud sizing): the chat's size pick is never behind Labs

Leo (2026-10-30): "WHAT GOOD REASON DO WE HAVE IN THE FIRST PLACE TO KEEP THIS SHIT IN FUCKING
LABS IF I FUCKING ASKED FOR THESE FUCKING FEATURES". CloudChatPick.read no longer checks Labs,
so drop the requiresLabs parameter added in 4c71cc8: every caller (cloud.startChat, Move to
Cloud, the setup wizard) sizes the chat's machine.

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

* cloud.startChat: the machine pick, the send's pin and keys and the copy move to cmux-tui

Leo, 2026-10-30: "AS MUCH RUST AS POSSIBLE INSTEAD OF SWIFT" and "SPEED UP THESE
FUCKING ITERATIONS". `cmux link start-chat plan|pin|copy` owns which machine a
first send takes, the per-send ledger (now in the state folder, locked), the
create/tab/place keys and the copy that never runs twice; the App lists its
machines, creates the one asked for, opens the chat and closes its tab.

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

* start-chat: Flags owns its lifetimes; rustfmt

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

* start-chat: clippy

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

* cloud.startChat: one Cloud machine per Mac workspace

Leo, 2026-10-30: "we'll want a vm for every single workspace".

The plan request now carries the Mac workspace the chat starts in. A send
from a workspace goes to that workspace's own machine: the one its first send
ran on, while it is listed, the person's and usable (a paused one resumes),
else a new one, which pin binds to the workspace. It never takes a free or
cloud-chats machine that other workspaces' chats share. A gone, teammate's or
failed machine drops the binding. A retry keeps its per-send pin first. The
ledger keeps up to 256 workspace bindings apart from the 64 sends and still
reads the older sends-only file.

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

* cloud.startChat: read the send's workspace through its optional target

Agent-Session: https://cmux.com/deeplink/agent?id=aid-515e927c20142b82a32c

* cloud.startChat: a send from no workspace never takes a workspace's own machine

Agent-Session: https://cmux.com/deeplink/agent?id=aid-515e927c20142b82a32c

* cloud.startChat: a retry of an older send never takes its workspace back

Agent-Session: https://cmux.com/deeplink/agent?id=aid-515e927c20142b82a32c

* cloud.startChat: the machine pick, the send's pin and keys and the copy move to cmux-tui

Leo, 2026-10-30: "AS MUCH RUST AS POSSIBLE INSTEAD OF SWIFT" and "SPEED UP THESE
FUCKING ITERATIONS". `cmux link start-chat plan|pin|copy` owns which machine a
first send takes, the per-send ledger (now in the state folder, locked), the
create/tab/place keys and the copy that never runs twice; the App lists its
machines, creates the one asked for, opens the chat and closes its tab.

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

* start-chat: Flags owns its lifetimes; rustfmt

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

* start-chat: clippy

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

* cloud.startChat: carry #29046 onto base with #28111's account and copy rules in Rust

#29046 moved the machine pick, the send's pin and keys and the copy to
cmux-tui, from a #28111 head that predates 15 of #28111's commits. This
brings those rules into the Rust instead of dropping them:

- Every send is kept under the account it began in (user and team): its
  pin, its copy and what makes a creatorless machine the person's own.
- The App takes machines only from the list the server answered for that
  account, keeps a pin to a machine made since (`stored`), creates in the
  send's team and rechecks the account around the create and the copy.
- Each copy goes to a new folder of the send's own, kept before the push:
  a retry whose record was evicted, one whose answer was lost, or a second
  copy at once never writes over another (SW-00 #2).
- A ledger that cannot be read refuses instead of reading as empty, and
  its bytes are moved aside as they are (SW-00 #1).

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

* start-chat: the account test expects the one machine team A's sends ran on

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

* cloud.startChat: the sends the App kept in its user defaults carry over to cmux-tui once

Router review at 83b8b35 (blocking): moving the ledger to cmux-tui dropped
the App's own pins on upgrade, so a send in flight could open its prompt on
another machine and a creatorless machine stopped counting as the person's.
The App hands its entries to `cmux link start-chat import` before the first
plan and forgets them once taken in; cmux-tui keeps its own entry for a send
it already has and leaves out sends kept under no account.

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

* start-chat: the place test moves its copy instead of cloning it (clippy redundant_clone)

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

* start-chat: the import test borrows its keys instead of cloning them (clippy redundant_clone)

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

* cloud.startChat: one Cloud machine per Mac workspace, on the carried cmux-tui start-chat

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

* ci: re-run the description checks on the edited body (no change)

The perf-section and risk checks read the description from the event
that started them; an edit through the broker started no new run.

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

* cloud.startChat: one Cloud machine per Mac workspace, on the carried cmux-tui start-chat

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

* cloud.startChat: a workspace keeps its machine while it pauses or resumes

With the shown status (#28407), a workspace's machine that is pausing or
starting (resuming) read as unusable while its link was down: its binding
was dropped and the next send made a second machine. Starting counts as
live, as CloudMachine.Status.isLive does, and pausing as paused.

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

* cloud.startChat: workspaces reuse and share machines; no machine per workspace

Leo, 2026-10-31: "THE IDEA WAS ONE WORKSPACE PER MACHINE BUT IDK WHY THAT
SHOULD BE THE LIMIT. JUST GO ARBITRARILY MANY" (leo-workspace
design/cloud.md, "Solo first, then sharing a machine", rule 2).

A Mac workspace's first send now reuses this person's machine (the one
for chats, else a free one) before a new one is made, and keeps the
machine it picked. Many workspaces share a machine, a send from no
workspace may take a machine a workspace picked, and nothing caps how
many machines a person has. When a workspace's machine is gone, a
teammate's or failed, its next send reuses another before making one.

The shared pick also skips a machine the server failed or is removing
while a stale link still reads as connected (SW-05, hq#5685), so a
workspace whose machine failed is never bound back to it.

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

* cloud.startChat: a machine the server destroyed is never kept as made since

Router review at f766845 (P1): a machine the server destroyed may stay in
the App's store after the list dropped it, and the App sent it as
`stored`, so a workspace (or send) pinned to it kept it and failed to
connect. The App now leaves failed, destroyed and deleting machines out
of `stored`; the pin or workspace moves on to another machine.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-bot[bot] <348531757+cmux-bot[bot]@users.noreply.github.com>
cmux-bot Bot pushed a commit that referenced this pull request Nov 1, 2026
…ked forms (#29799)

The tip panic ratchet is red at 19532c2 (tip watcher job
47df5bd8d21736ec724e85b2): cmux-tui `clippy::indexing_slicing` 832 > baseline
831 and `clippy::string_slice` 83 > baseline 82. The only cmux-tui change in
3c2f4ec..19532c2 is b06e339 (#29582, carry of #29046), which
added one of each:

- link/mod.rs: `start_chat_cli::run(&args[1..])` (indexing_slicing). Now
  `args.get(1..).unwrap_or_default()`. The branch is taken only when
  `args.first()` is "start-chat", so the slice is the same.
- link/start_chat_cli.rs `fresh_place`: `&id[..8]` of a uuid's simple form
  (string_slice). Now the first 8 chars; the form is 32 ASCII hex digits, so
  the result is the same 8 bytes.

No behavior change.
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Superseded: carried onto feat-cmux-next and landed as #29582.

This branch was previously deployed

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

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant