Skip to content

[Bug]: Switching the Claude account with a live Claude terminal writes the target account’s expired token into the shared credential, and every running session dies with “Login expired” #23885

Closed

Description

@paulkarayan

Operating system

macOS

Orca version

v1.4.215

Details

Short summary

With at least one Claude terminal running, selecting a different managed Claude account copies that account’s stored OAuth credential into ~/.claude/.credentials.json and both Claude Code-credentials keychain slots without refreshing or validating it first. If the stored access token has expired and its refresh token no longer works, every running Claude session reads the dead credential. About 20 sessions across different worktrees died with Login expired. Please run /login within 90 seconds of one switch.

Related, but not the same path: #13165 (stale copy written over ~/.claude/.credentials.json) and #20251 (the live-PTY refresh gate never opens). This report is about the account-switch path specifically.

What happened (one machine, 2026-09-28, all times UTC. A read-only watcher logged the file mtime, expiresAt and keychain modification dates once a minute. No token values were read.)

Time | Event -- | -- 22:34 | Switched to account A. The token written into ~/.claude/.credentials.json had expiresAt 13:34, nine hours earlier. No deaths: a running claude refreshed it at 22:51 and Orca read the refreshed token back. 23:35:14 | Switched to account B. The token written had expiresAt 22:16, 80 minutes earlier. 23:35:33 to 23:35:43 | Both Claude Code-credentials and Claude Code-credentials- were rewritten with the same token. 23:35:37 to 23:38 | 36 Login expired errors across about 20 sessions in different worktrees. In 4 minutes no session managed to refresh B’s token (the file was rewritten at 23:37 with the same expired token). 23:39 | Switched back to A. The sessions recovered after /login. 23:54 | Switched to B again. The same expired token was written again, and 4 more sessions died. 00:01 | Switched to account C. Its stored token had expiresAt 22:18. One more session died. 00:02 | Settings > Accounts > Re-authenticate on C produced a fresh token (valid until 08:02). No further switch deaths.

The 22:34 row is the control. An expired access token alone is survivable, because Claude Code refreshes it. The switch is only fatal when the target account’s stored refresh token is also dead. Orca never checks that before writing it into the slot every session reads.

Where it happens (names from the minified 1.4.215 bundle, out/main/index.js)

In doSyncForCurrentSelection:

let l = Xp();                       // Xp() = Hp.size > 0: any live Claude PTY
if (l && v9i(c)) this.managedRefreshDeferredByLivePtyAccountId = i.id;
if (!l) { let e = await this.refreshManagedAccountTokenIfNeeded(i, c); e && (c = e) }
// ...then unconditionally:
this.writeRuntimeCredentials(c);    // ~/.claude/.credentials.json
await $e.l(c, u.configDir);         // keychain: hashed slot AND bare "Claude Code-credentials"

v9i is “expires within 5 minutes”. With any live Claude PTY, the refresh is skipped, and the stored credential c is written to the shared runtime slots even when v9i(c) is true, meaning it has already expired. The deferral makes sense for the account that live PTYs are currently rotating. On a switch, though, the target is a different account whose refresh token no live PTY holds, so the reason for deferring does not apply.

The status-bar account menu already refreshes expiring inactive tokens when it opens (rateLimits:fetchInactiveClaudeAccounts refreshes and persists them). That fetch is throttled to once every 60s. A switch does not wait for it, and it does not check whether it succeeded.

How to reproduce

  1. Add two managed Claude accounts, A and B. Let B sit unused for more than 8 hours so its stored access token expires. To make the failure fatal rather than self-healing, make B’s stored refresh token stale. Here that happened after another tool refreshed B outside Orca.
  2. With A active, start one or more claude sessions in Orca terminals and leave them running.
  3. Select B from the status-bar account menu or from Settings > Accounts.
  4. Within about a minute, the running sessions fail with Login expired. Please run /login (authentication_failed). ~/.claude/.credentials.json holds a claudeAiOauth.expiresAt in the past.

Expected

On a switch, Orca should make sure the target credential is live before writing it into the shared slots:

  1. If the target token is expired or expiring, refresh it even when live PTYs exist. No live PTY is rotating the target account’s lineage.
  2. If the refresh fails, do not materialize the dead credential. Keep the current account, and show “Re-authenticate B” on the account row.

Also observed, less certain

At 00:13 on the same night, with no account switch, ~/.claude/.credentials.json and the hashed keychain slot were deleted, then re-created 15 to 30 seconds later with the same token. Four sessions died with Login expired inside that window. The only code I found that removes both is clearRuntimeAuthForAccount / restoreSystemDefaultSnapshot. I have not established what triggered it. A write that replaces the credential in place, with no delete first, would close that window too.

Anything else that might help

  • About 20 concurrent claude sessions, all launched from Orca terminals. Claude Code reads the bare keychain slot or the CLAUDE_CONFIG_DIR-hashed slot.
  • I can share the watcher log. It holds timestamps, expiry times and 8-hex token fingerprints, never token values.

Activity

  1. added theissue type on Sep 29, 2026
  2. OrcaWin commented on Oct 11, 2026

    @OrcaWin
    Collaborator

    Thanks for reporting. This is fixed on main by #26801: each managed Claude account now runs from its own folder, so switching accounts no longer rewrites a shared credential under running sessions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingos:macosIssues affecting macOS

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions