After switching accounts, Keychain holds the new account's OAuth token but all identity surfaces still report the previous account
Environment
- Claude Code 2.1.220 (native install, auto-updated from 2.1.219)
- macOS (Darwin 25.5.0), zsh
- Login method: claude.ai subscription (OAuth), credentials in macOS Keychain ("Claude Code-credentials")
- No
CLAUDE_CODE_OAUTH_TOKENorANTHROPIC_API_KEYset; default config dir (~/.claude); noapiKeyHelper/forceLoginMethodin settings
Summary
I use two claude.ai accounts on the same machine (think work and personal; both have been signed into Claude Code on this machine before). After logging out of Account A and logging into Account B via the normal logout/login flow, Claude Code ended up in a split state that persisted for days, across many sessions and background token refreshes:
- The Keychain OAuth token is Account B's (verified server-side:
GET https://api.anthropic.com/api/oauth/profilewith the Keychain access token as Bearer returns Account B's profile and org). - But every identity surface still reports Account A:
oauthAccountin~/.claude.json(email, accountUuid, org, and plan/rate-limit tier are all Account A's — the tier shown doesn't match the pool the token actually draws from)claude auth status(returns Account A's email/org/subscription)- the
accountUuidstamped oncachedUsageUtilizationin~/.claude.json oauthAccount.profileFetchedAtkeeps advancing (a periodic profile refresh is clearly running), yet the stale Account A identity is never corrected to match the live token.
Impact
- All usage/limit displays show Account B's real numbers labeled as Account A. The smoking gun: the
resets_attimestamps incachedUsageUtilizationmatch Account B's claude.ai usage page to the second, and don't match Account A's at all. Until spotting that, there was no way to tell which subscription was actually being consumed — several days of usage landed on the unintended account. - Claude-in-Chrome refuses to connect with: "the OAuth token Claude Code is using belongs to a different claude.ai account than the one Claude Code is logged in as." Credit where due: this extension handshake is the only surface that cross-checks token vs. label server-side, and it's what surfaced the bug.
Timeline / possibly relevant details
- The switch was done ~4 days before detection; the Keychain item's creation date confirms it was deleted and recreated at that time (so logout/login did replace the token).
- Both accounts had previously been used on this machine, so the login may have gone through the returning-account picker rather than a fully fresh browser auth.
- A background process refreshes the token regularly (
daemon.log: "auth: proactive refresh succeeded"), so the Account B token lineage kept rotating forward while the Account A labels persisted.
Expected
After /logout + /login as Account B, all identity surfaces (claude auth status, oauthAccount, usage attribution) should report Account B — or, failing that, Claude Code should detect and surface a token/identity mismatch the way the Chrome extension does.
Repro (as experienced; may depend on the returning-account picker)
- Sign into Account A, use normally.
/logout, then/loginand authenticate as Account B.claude auth status→ still reports Account A.- Verify token owner: read the Keychain item and call
/api/oauth/profilewith the access token → Account B.
Workaround for anyone hitting this
Trust only server-side identity: probe /api/oauth/profile with the Keychain token, or compare cachedUsageUtilization reset timestamps against each account's claude.ai usage page. A clean /logout + /login (and re-verifying afterwards) resolves the split.