Repository navigation
cloud.startChat: a new machine whose older cmux-tui refuses the copy is updated and copied again (re-land in CmuxNextCloud) - #30610
Conversation
…is updated to the app's build and copied again (re-land in CmuxNextCloud) Re-lands the behavior of #30258 (reverted by #30438 for raising CmuxNextApp's line count): the decision and the copy-update-copy sequence live in CmuxNextCloud (CloudOutdatedMachine), and CloudMove.holds moves there as CloudWait.holds (no behavior change), so CmuxNextApp shrinks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-3bab30503f0b9a7059e5
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
Not verified: macOS jobs did not run: compile and app tests were skipped on CI passes on Written by Shipped in:
|
There was a problem hiding this comment.
2 issues found across 9 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="Packages/macOS/CmuxNext/Sources/CmuxNextCloud/Move/CloudWait.swift">
<violation number="1" location="Packages/macOS/CmuxNext/Sources/CmuxNextCloud/Move/CloudWait.swift:10">
P3: This adds a second bounded-observation implementation alongside `AgentPaneMachineLink.settles`; extract the common primitive so timeout and cancellation fixes cannot diverge.</violation>
</file>
<file name="Packages/macOS/CmuxNext/Sources/CmuxNextApp/Cloud/CloudStartChat.swift">
<violation number="1" location="Packages/macOS/CmuxNext/Sources/CmuxNextApp/Cloud/CloudStartChat.swift:119">
P1: `updateNow` returns `true` for any completed update turn, not only a successful machine update, so this retries the copy after failed, busy, unsupported, or no-op updates. Return and propagate the update outcome, and retry only when the machine actually reached a usable build.</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
| copy = try await CloudOutdatedMachine.copy({ | ||
| try await cli.startChatCopy(key: start.key, account: Self.accountText(account), machine: machine.machineID, socket: socket, | ||
| local: folder.root, subpath: folder.subpath, workRoots: services.cloud.move.workRoots) | ||
| }, answer: { ($0.0.ok, $0.0.errorCode, $0.0.side) }) { await CloudMachineUpdates.updateNow(machine.machineID, services: services) } |
There was a problem hiding this comment.
P1: updateNow returns true for any completed update turn, not only a successful machine update, so this retries the copy after failed, busy, unsupported, or no-op updates. Return and propagate the update outcome, and retry only when the machine actually reached a usable build.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Sources/CmuxNextApp/Cloud/CloudStartChat.swift, line 119:
<comment>`updateNow` returns `true` for any completed update turn, not only a successful machine update, so this retries the copy after failed, busy, unsupported, or no-op updates. Return and propagate the update outcome, and retry only when the machine actually reached a usable build.</comment>
<file context>
@@ -112,8 +112,11 @@ struct CloudStartChat {
+ copy = try await CloudOutdatedMachine.copy({
+ try await cli.startChatCopy(key: start.key, account: Self.accountText(account), machine: machine.machineID, socket: socket,
+ local: folder.root, subpath: folder.subpath, workRoots: services.cloud.move.workRoots)
+ }, answer: { ($0.0.ok, $0.0.errorCode, $0.0.side) }) { await CloudMachineUpdates.updateNow(machine.machineID, services: services) }
} catch {
// Timed out or didn't launch: Retry copies again on this machine, into a new folder.
</file context>
| public static func holds(within limit: Duration, until condition: @escaping @MainActor @Sendable () -> Bool) async -> Bool { | ||
| if condition() { return true } | ||
| // concurrency-allow: the observation task exits on cancellation; this bounds a machine link's start | ||
| return await withTaskGroup(of: Bool.self) { group -> Bool in |
There was a problem hiding this comment.
P3: This adds a second bounded-observation implementation alongside AgentPaneMachineLink.settles; extract the common primitive so timeout and cancellation fixes cannot diverge.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Sources/CmuxNextCloud/Move/CloudWait.swift, line 10:
<comment>This adds a second bounded-observation implementation alongside `AgentPaneMachineLink.settles`; extract the common primitive so timeout and cancellation fixes cannot diverge.</comment>
<file context>
@@ -0,0 +1,26 @@
+ public static func holds(within limit: Duration, until condition: @escaping @MainActor @Sendable () -> Bool) async -> Bool {
+ if condition() { return true }
+ // concurrency-allow: the observation task exits on cancellation; this bounds a machine link's start
+ return await withTaskGroup(of: Bool.self) { group -> Bool in
+ group.addTask { await observe(condition) }
+ group.addTask {
</file context>
|
Review (subagent claude-opus-5-5 medium): SHIP at 691c7a3 No blocking findings. The Follow-ups (filed as one issue):
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-3bab30503f0b9a7059e5
Re-lands #30258 (reverted by #30438: "CmuxNextApp +21 lines turned the feat tip arch ratchet red; re-land with the code moved to a package").
Leo's ask this serves: "whenever i start a chat it should be able to send me to cloud automatically keep launching/downloading new dev builds and such"
What it does
A new Cloud machine's image may carry an older cmux-tui than the app, and the automatic update its first handshake queued may not have run yet.
cloud.startChat's folder copy (cmux link start-chat copy) refused withmachine_outdatedabout the machine (notside: "source") now updates the machine to the app's build (CloudMachineUpdates.updateNow,--to-bundled --if-older) and copies once more, instead of failing the first send with "Update the machine". Any other answer, or no update turn (toggles off, no client, machine gone), keeps the first answer.Where the code lives (net CmuxNextApp lines: -16)
CmuxNextCloud/Move/CloudOutdatedMachine.swift(new):code(machine_outdated), the predicateupdatesBeforeCopyingAgain(ok:errorCode:side:), and the generic copy, update, copy-again sequence over closures.CmuxNextCloud/Move/CloudWait.swift(new):CloudMove.holds(within:until:)moved verbatim asCloudWait.holds, to offset the call site (a move with no behavior change; its 6 callers renamed).CloudStartChat.swift: the copy goes throughCloudOutdatedMachine.copy.CloudMoveStrings.outdatedCodenow readsCloudOutdatedMachine.code.CloudStartChatMachineTests.onlyTheMachinesOutdatedRefusalUpdatesItBeforeCopyingAgaincovers every answer kind and whether the update ran or not.Arch ratchet (
scripts/cmux-next/arch-ratchet.py --audit):app_lines159583 at base 00085a3, 159567 at head.arch-ratchet.py --base ... --head HEADandguardrails.pysay "no architecture count rises";check-concurrency.shpasses.Review follow-up from #30258: wait for the daemon to reconnect
#30258 read the socket again from the app's own connection after the update, which may be nil or stale right then. This version copies again on the same socket and does not need the app's own connection to be back first:
cmux link machine updateanswersupdatedonly after the new daemon answered over that socket:wait_for_outcomereads the outcome andidentifythrough it (cmux-tuilink/machine_update.rs:334-366), so whenupdateNowreturns (it awaits the policy turn,CloudMachineUpdates.swift:94-98), the socket reaches the new daemon. The socket is the localcmux-tui remote connectlink's, which redials, and each CLI dials its own connection over it (CloudMachineLink.swift:4-13).CloudMove.swift:151-156).updateNowdoes not wait for the app's own reconnect: the target callssource.reconnect(CloudMachineUpdateTarget.swift:111), andreconnectAfterUpdate(CloudMachineSession.swift:451) starts the connect without awaiting it. The open after the copy waits for that connection's tree for up to 10 s (CloudHandlers.existingWorkspace,CloudHandlers.swift:246-252).Risk: low. The change is limited to the start-chat copy's
machine_outdatedpath, and the moved wait helper is unchanged.Agent session: agent session
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.Summary by cubic
Re-lands
cloud.startChat's handling of a new Cloud machine whose older cmux-tui refuses the folder copy withmachine_outdated: the machine is now updated to the app's own build and the copy runs again, instead of failing the first send with "Update the machine". Any other answer, or an update that didn't run, keeps the first answer.The logic moved into the
CmuxNextCloudpackage —CloudOutdatedMachinefor the refusal handling, andCloudMove.holdsmoved verbatim toCloudWait.holds— soCmuxNextApp's line count drops.Written for commit 691c7a3. Summary will update on new commits.