fix(cloud): signed-out Cloud actions sign in, then run
Fixes HQ research finding "Signed-out cloud is text only" (fresh-home GUI audit, finding 2):
Signed-out cloud is text only (code-read). Menus and the palette say "Sign in to use Cloud machines: run Sign In from the command paletteā¦" with no button (CloudHandlers.swift:19).
Leo, on the audit: "Are we covering the edge cases for when, you know, something isn't going right, if there's recovery, whatever, stuff like that."
#27104 made New Cloud Machine sign in and then resume. Every other Cloud action (machine, browser, network, domain, GitHub, New Cloud Workspace, start-session) was still greyed out, with only that text.
What changes
- Registry:
ActionRecovery(CmuxNextActions/ActionRecovery.swift). This is a way past an unavailable reason that only a person's gesture can take. The action keeps its reason, sounavailableReason(for:), the control bridge's up-front refusal and the agent action list are all unchanged. For a run whose origin isuser(including the run it is nested in):performruns the recovery instead of returning false.- The menu and context-menu row validates as enabled.
- The palette row is enabled, and its subtitle is the recovery's note.
- Cloud (
CloudHandlers+Account.swift, wired fromCloudHandlers.bind):- Any action whose current reason is
CloudStrings.signInFirstgets a recovery. It signs in through the same one-pending-replay continuation New Cloud Machine uses (CloudSignedOutCreationContinuation, #27104), then runs the action with the person's original invocation. - Before the replay, it waits for a current machine list for the signed-in account, so Open Desktop or SSH doesn't replay into an empty machine list.
- A later intent, a sign-out or a team change drops the pending replay.
- A reason that still holds after sign-in (no backend, no team) is reported instead of being dropped silently.
- Sign Out and Switch Active Team are exempt.
- Any action whose current reason is
- Text: the palette note for these rows is a new string,
cloud.signIn.toContinue: "Sign in to use Cloud machines: choose it to sign in, then it runs." It is in all 21 locales (en and ja translated, the restneeds_review). The CLI, MCP and scripts still get the existingsignInFirsttext withcmux cloud sign-in.
No default changes. No surface is removed. Sign In stays where it was.
Review
Codex gpt-6-astra: SHIP at 9bf6b088ca64 with no follow-ups. Receipt and default-change note: see the PR comments. Earlier follow-up filed: manaflow-ai/cmuxterm-hq#5531.
Risk
Medium. The change touches ActionRegistry.perform, menu validation and the palette's enabled state, but only for actions a registered recovery answers. Today that is only Cloud's signed-out reason. Every other disabled action follows the old path, because personRecovery returns nil when nothing is registered or nothing answers. Automation can't trigger a sign-in: a run whose origin is cli, mcp, script, page or remote, or a person's invocation nested in a CLI run, is refused with the reason as before.
Tests
ActionRecoveryTests(CmuxNextActionsTests) checks four things:- A person's run takes the recovery and not the handler.
- Every automation origin, and a run nested in a CLI run, is refused, and the reason is kept.
- A reason no recovery answers stays disabled.
- The menu row validates as enabled only while a recovery answers.
PaletteRecoveryRowTestschecks that the row is enabled with the note, runs the recovery, and shows no subtitle once signed in.CloudSignInRecoveryTestschecks that onlysignInFirstrecovers, that Sign Out and the team picker are exempt, and that the note no longer points at the command palette.
Captures
Capture state: scripts/cmux-next/media/states/cloud-signed-out-actions.json. It shows the palette's Open Desktop row and a fixture Cloud machine's right-click menu, both signed out. debug.cloud_machine_fixture {backend: true} is DEBUG only. It makes Cloud read as reachable on a capture build with no backend, so the rows show the signed-out reason. Before = base + that state commit (lane/cloud-signin-scene-base, 42c1940a903b); after = this head. The light and dark stills are being captured on the fleet and will be added here.
Design spec: no design/<surface>.md exists for the palette's disabled-row reasons. The palette row keeps its layout; only the subtitle text and the enabled state change.
Agent session: agent session
š¤ Generated with Claude Code