Repository navigation
cmux NEXT Cloud: a first chat an older daemon refused updates the machine and starts once more - #29497
Conversation
…hine and starts once more A fresh Cloud machine boots its image's cmux-tui (images/cmux-vm pins 308bd36, from before #29107), so its daemon refuses its creator's chat on a team VM with agent_session.team_vm_blocked. The app's automatic update of that machine runs in the background from its handshake, so a chat started right after the machine came up (cloud.startChat, cmux cloud start-session, the first send) reached the old daemon. Now such a refusal (team_vm_blocked or host_unverified) on the caller's own machine (the link's owner answer) runs the same update at the front of the shared DaemonUpdatePolicy queue (DaemonUpdatePolicy.updateNow, joining an update of that machine that already runs), waits for its turn to end, and starts once more. A teammate's machine is never retried; any other refusal stands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-3bab30503f0b9a7059e5
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
web and size checks: green Shipped in:
|
|
Owned by an agent session · session |
|
Review (codex, router-review): SHIP at a48ab78 Reviewer: codex gpt-6-astra high, fast tier. Follow-ups: 2. Each lands as an issue in this repo that links this receipt; the lane that opened the PR fixes it forward. SHIP at a48ab78 Visual verdict: no images; Leo tries the build
Static review completed; Swift tests could not run because this Linux box has no Swift toolchain. Follow-ups:
|
…st is due now and outlasts a turn that never reached its update Review of #29497 (follow-ups #29498, #29499) and the concurrency gate's unbounded AsyncStream. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-3bab30503f0b9a7059e5
…first-start-updates-machine
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
|
Review (codex, router-review): SHIP at c85e0f1 Reviewer: codex gpt-6.1-sol xhigh, fast tier. Follow-ups: 2. Each lands as an issue in this repo that links this receipt; the lane that opened the PR fixes it forward. SHIP at c85e0f1
Follow-ups:
|
…first-start-updates-machine # Conflicts: # Packages/macOS/CmuxNext/Sources/CmuxNextApp/AgentTabRemoteHost.swift # Packages/macOS/CmuxNext/Sources/CmuxNextApp/AgentTabs+Wiring.swift
|
Found 4 test failures on Blacksmith runners: Failures
|
|
Review (codex, router-review): SHIP at e154c9d Reviewer: codex gpt-6.1-sol xhigh, fast tier. Follow-ups: 2. Each lands as an issue in this repo that links this receipt; the lane that opened the PR fixes it forward. SHIP at e154c9d Visual verdict: no images; Leo tries the build
Follow-ups:
|
|
Review (codex gpt-6-astra high, router-review): BLOCK at 872a507 BLOCK at 872a507 Visual verdict: no images; Leo tries the build
Follow-ups:
|
…g update never runs it twice Review of #29497 at 872a507: #28271 removed DaemonUpdatePolicy.shared (the policy is services.daemonUpdates.policy), and a refusal that arrived while the machine's update was inside target.update re-marked it, so a failed update ran a second time. The policy now marks the target inside its update and updateNow only waits for that turn. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-3bab30503f0b9a7059e5
|
Review (codex, router-review): SHIP at 7cc32b6 Reviewer: codex gpt-6-astra high, fast tier. Follow-ups: 1. Each lands as an issue in this repo that links this receipt; the lane that opened the PR fixes it forward. SHIP at 7cc32b6 Visual verdict: no images; Leo tries the build
Source review completed; tests could not run because this Linux box has no Swift toolchain. Follow-ups:
|
… older-daemon codes, moves to CmuxNextDaemonService (app_lines +51) (#29590) * base red: Takes back #29497: the remote-start refusal table, with its older-daemon codes, moves to CmuxNextDaemonService (app_lines) 270912b (#29497, "a first chat an older daemon refused updates the machine and starts once more") adds 51 CmuxNextApp Swift lines with no exception row; arch-ratchet's app_lines row refuses it alone (--base 270912b^ --head 270912b: +51), part of the tip's red (STATUS, lead 18:5xZ). - #29497's own refusal codes (AgentTabRemoteHost.olderDaemonRefusals) move, with their doc, onto the refusal type as AgentRemoteStartRefusal.olderDaemonCodes. - The refusal type itself (AgentRemoteStartRefusal: the one table from an `agent-session-start` refusal to the person's words, through RefusalStrings) and AgentTabRefusedHost move unchanged in behavior from CmuxNextApp/AgentTabs+RemoteStart.swift to CmuxNextDaemonService, next to RefusalStrings, whose Refusals table they already read (same bundle, same keys). Public access, an explicit init with the same defaults. The App reaches them through its CmuxNextDaemonService re-export; the tests are unchanged. * The folder-needed code keeps its App declaration (defaults guard); the moved refusal uses its own constant, a test keeps them equal The defaults guard tracks AgentRemoteStartRefusal.folderNeededCode by its file path (it is referenced from CloudStartSession.startFailure) and reported its move as a changed default. The App keeps `static let folderNeededCode = "app.folder_needed"` in AgentTabs+RemoteStart.swift, as an extension of the moved type; the moved type uses an internal appFolderNeeded for folderNeeded(machine:) and its message. AgentRemoteStartRefusalCodeTests asserts the two are the same code.
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Leo: "whenever i start a chat it should be able to send me to cloud automatically".
What was wrong
A freshly created Cloud machine refused its creator's Claude chat with
agent_session.team_vm_blocked("Agent chats cannot be started from this Mac on a team VM yet"). The refusal came from the machine's own cmux-tui.images/cmux-vm/inputs.lock.jsonpins308bd36969c8(cmux agents snapshot: report GUI agent chat tabs as chats, not about:blank browsers #27936, Oct 29), which predates Cloud: a machine's creator can start chats on it from the app, team machines included #29107 (c8af668). Cloud: a machine's creator can start chats on it from the app, team machines included #29107 is the change that lets a team VM take its creator's chat on the Cloud API'svmKind: "owner"word.vm_kind: "owner"whenever the daemon advertises it, and the API'scallerVmKindgives the creatorowner. The old daemon refuses a team VM whatever word it gets.cmux link machine update --to-bundled --if-older,CloudMachineUpdatesandDaemonUpdatePolicy). That update runs in the background from the machine's handshake, and nothing waits for it. A chat started right after the machine comes up (cloud.startChat,cmux cloud start-session, a first send) reaches the old daemon. Leo's VM worked only after a newer cmux-tui was copied into~/.cmux/bin/cmux-tuiby hand.What changes
A start that the machine's older daemon refuses with
team_vm_blockedorhost_unverifiednow updates the machine and starts once more. This only happens on the caller's own machine, meaning the link's owner answer is present.DaemonUpdatePolicy.updateNow(_:)is new.agent.updatePeersis off, or when the start is cancelled.AgentTabRemoteHost.updateMachineis new.keyedStartruns it on those two refusals, re-runs the start'sproceedcheck (the account check), then tries the start once more.CloudMachineUpdates.updateNowbuilds the same target as the background offer, so the update keeps the same guarantees:--if-older, never a move back;updateMachineonly for Cloud machines. It calls it only while the link's owner answer is present. A teammate's machine is still refused by the app before any start is sent, and is never retried.The new logic stays in the existing Swift update policy and host. The update itself is already Rust (
cmux link machine update), and this adds only the "wait for it, then start again" glue around it.Tests
DaemonUpdatePolicyTestscoversupdateNow:CloudStartSessionTests.aStartAnOlderDaemonRefusedUpdatesTheMachineAndStartsOnceMorecovers:proceed, then a second start;Risk
Not in this PR
Agent session: agent session
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.Summary by cubic
A first chat on a fresh Cloud machine now survives the machine's older daemon: when the daemon refuses the start with
team_vm_blockedorhost_unverified, the app updates the machine to its own build and starts once more.Written for commit 7cc32b6. Summary will update on new commits.