Subscription-OAuth long-running Remote Control session dead-ends at 401 / "Please run /login" at the daily refresh boundary; /login cannot complete in a remote-controlled session
Subscription-OAuth long-running Remote Control session dead-ends at 401 / "Please run /login" at the ~daily token-refresh boundary; /login cannot complete in a remote-controlled session
Environment
- Platform: macOS (darwin 25.5.0)
- Claude Code: 2.1.198 (native installer). Note the failing process had been running continuously for ~27h and was still executing the in-memory 2.1.197 build (a native auto-update repointed the on-disk binary to 2.1.198 mid-session, without the running process reloading — see "Secondary observation").
- Auth: Claude subscription OAuth login, credentials in the macOS Keychain (
Claude Code-credentials). NoANTHROPIC_API_KEY/CLAUDE_CODE_OAUTH_TOKEN/ANTHROPIC_AUTH_TOKENset; no~/.claude/.credentials.json. - Usage: one long-running (24h+) session ("captures") driven from the Claude iPhone app Remote Control feature, running concurrently with other local
claudeCLI sessions and the phone app — all three sharing the one Keychain OAuth credential.
Summary
A long-running session that is being driven via iPhone Remote Control periodically dies with:
⏺ Please run /login · API Error: 401 Invalid authentication credentials
This recurs roughly every ~24 hours, i.e. at the access-token-expiry / refresh boundary. It appears to be the subscription-OAuth sibling of a bug class that has been fixed many times before (see "Related prior fixes"), and specifically the sibling of the AWS/Mantle STS fix shipped in 2.1.198. The usage pattern (long remote session + concurrent local sessions + phone app) is unchanged from months of prior trouble-free use, which points to either a recent regression or a server-side token-policy change (shorter TTL / stricter single-use rotation) re-exposing the fundamental multi-client shared-credential race.
Two distinct problems
1) The 401 itself (root cause: multi-client refresh-token race)
The captures session, concurrent local CLI sessions, and the phone app share one Keychain OAuth credential with single-use rotating refresh tokens. When a concurrent client refreshes, the server invalidates this session's refresh token; when the long session later crosses its access-token expiry and tries to reactively refresh, its refresh token is already spent → 401 → "Please run /login".
2) /login cannot recover a remote-controlled session in place
When the user runs /login inside the remote-controlled session:
- Browser opens, user authorizes, browser shows the "You're all set up for Claude Code / You can now close this window" success page.
- The CLI never receives the OAuth loopback callback and stays at
Login OAuth error: Press Enter to retry. - Pressing Enter re-opens the browser (skips re-auth, straight to Authorize→success) but the CLI never advances.
The interactive loopback callback does not return through the Remote Control channel, so in-place /login can never complete. Historically the only "fix" was quitting and starting a brand-new local session.
Workaround that works
Pressing ESC to cancel the stuck /login prompt and then retrying the request lets the session resume — i.e. the built-in reactive-refresh-on-401 (fixed in 2.1.117) succeeds once the shared credential has been refreshed out of band. So the interactive /login is both doomed and unnecessary; ESC + retry is the correct recovery, but this is non-obvious and the UI actively steers the user toward the doomed /login.
Expected behavior
- A long-running subscription-OAuth session should reactively refresh at 401 and recover without surfacing "Please run /login", even when other clients (phone app, concurrent CLI sessions) share the credential — analogous to the
awsAuthRefreshfix for the AWS/Mantle path in 2.1.198. - If re-login is genuinely required, it must be completable from within a Remote-Control-driven session, or the CLI should clearly tell the user to ESC + retry / start a fresh local session rather than hang at "Login OAuth error".
Related prior fixes (all already present in the running build)
- 2.1.117 — Plain-CLI OAuth sessions dying with "Please run /login" when the access token expires mid-session; now refreshed reactively on 401.
- 2.1.118 — macOS keychain race where a concurrent token refresh overwrites a fresh OAuth token, causing unexpected "Please run /login"; OAuth refresh failing when the server revokes a token before local expiry.
- 2.1.126 — Concurrent credential write clearing a valid OAuth refresh token.
- 2.1.133 — Parallel sessions all dead-ending at 401 after a refresh-token race wiped shared credentials.
- 2.1.136 — Login loop where a concurrent write overwrites a freshly-rotated OAuth token and forces re-login.
- 2.1.169 — Remote Control stuck reconnecting when an OAuth refresh happens concurrently.
- 2.1.178 — Model requests still failing after credentials were refreshed outside the session (stale cached request config).
- 2.1.198 — AWS/Mantle sessions dead-ending at "Please run /login" when the STS token expires (the sibling of this bug, on a different auth backend).
Related open feature requests: #36807 (Remote Control sessions should persist without interactive re-login) and #50977 (support setup-token/API-key auth for Remote Control) — relevant because a static CLAUDE_CODE_OAUTH_TOKEN would immunize against the race but currently disables Remote Control (#33105, #59062), leaving no configuration that closes the race while keeping phone control.
Secondary observation (version skew under long sessions)
With autoUpdates: false set in ~/.claude.json, the native installer still auto-updated (2.1.197 → 2.1.198) mid-session and repointed the claude symlink, while the long-running process kept executing 2.1.197 in memory for ~20h. Long-running sessions should either honor autoUpdates: false for the native updater as well, or make the in-session version skew visible so users know to restart after an update lands.