fix(cloud): signed-out Cloud actions sign in, then run

Merged
TE
6d ago
+434-7

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, so unavailableReason(for:), the control bridge's up-front refusal and the agent action list are all unchanged. For a run whose origin is user (including the run it is nested in):
    • perform runs 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 from CloudHandlers.bind):
    • Any action whose current reason is CloudStrings.signInFirst gets 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.
  • 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 rest needs_review). The CLI, MCP and scripts still get the existing signInFirst text with cmux 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.
  • PaletteRecoveryRowTests checks that the row is enabled with the note, runs the recovery, and shows no subtitle once signed in.
  • CloudSignInRecoveryTests checks that only signInFirst recovers, 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