Cloud start: replace a Claude login the provider rejects

Merged
TE
4d ago
+273-10

The coordinator's ask: "when the provider rejects the Claude login on a machine, the start must replace it with the app's fresh login instead of skipping the install, then retry the turn once."

Why

A Cloud start (cloud.startChat, DEV and dogfood builds) skipped passing the account's Claude login whenever the machine already had any login file (cmux_agent_has_own_login claude). A reused machine holding an expired or revoked token then failed every turn with "The provider rejected Claude Code's credentials on this Cloud machine. Set them up again to continue." while cmux held a fresh cached login for that account. Seen in the Cloud start proof's no-folder item at feat-cmux-next df4bd224: the reused "cloud-chats" machine had a stale login from an earlier run.

Change

  • New CloudMachineLogin in CmuxNextCloudCredentials (no new App logic; CmuxNextApp loses lines). One exec on the machine reads the login Claude would use, as the work user's: none, session (a /login with a refresh token, which Claude renews itself), cached (the very token cmux holds, compared by SHA-256 on the machine, so no token is sent there), working, or rejected (the provider answered 401 to GET /v1/models with that token as an OAuth bearer).
  • passHarnessLogin treats rejected like no login: a cached login installs silently, else the existing one-time confirm. Every consent rule is unchanged (caller owns the VM, owner guest, the transfer's pre/post checks, DEV/dogfood gate).
  • Never loops: one probe per start, one install. A machine that already has cmux's token is never replaced again, whatever the provider says. A session, a 403, an unreachable provider and any other answer leave the machine's login alone. The start opens its chat after the install, so its first prompt runs on the new login and needs no retry.

Not in this PR

A token that expires mid-life (after the start) still goes through the turn card's existing recovery ("Sign in to Claude", then Transfer and restart, which retries the prompt once). An automatic replace-and-retry on that path needs the page to retry without a gesture ticket, so it is a follow-up.

Risk

If the provider ever answers 401 to a working token for this probe (the OAuth beta header changes), a start replaces that machine's login once with the account's own; the cached check stops it from repeating. The probe gives up after 8 s and reads as "keep".

Tests

CloudMachineLoginTests runs the real probe (shell plus Python) against a scratch machine with the image's agent-auth.sh and a local provider: no login, then 401 is rejected, 200, 403 and unreachable are kept, cmux's own token is kept on a 401, and a refreshable session is left alone. It also checks that the token goes only in the bearer header and never in the output. CloudStartChatLoginTests is updated.

Agent session: agent session

šŸ¤– Generated with Claude Code


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


Summary by cubic

Cloud start now replaces a Claude login the provider rejects on a reused machine, instead of skipping the install and failing every turn.

Previously, cloud.startChat skipped passing the account's Claude login whenever the machine had any login file, so a reused machine with an expired or revoked token failed every turn with "The provider rejected Claude Code's credentials on this Cloud machine" while cmux held a fresh cached login.

Now, CloudMachineLogin reads the machine's login before the start passes the account's own: a 401 from the provider marks it rejected and it's replaced like a missing one, through the same transfer and consent checks.

  • The probe compares cmux's cached token by SHA-256 on the machine, so no token is sent there; a machine that already has it is never replaced, so a start never loops.
  • Sessions, 403s, unreachable providers, and any other answer leave the machine's login alone, including a Python failure after the image's own detection found a login (the login is kept rather than read as missing).
  • The classic /api/vm exec runs as root, so the probe points HOME at cmux's home before reading; it must not misread a working login there as missing and replace it.
  • A token expiring after the start still goes through the turn card's existing recovery.