After switching accounts, Keychain holds the new account's OAuth token but all identity surfaces still report the previous account

Status Open
Reported on v2.1.220
Maintainer reply None cached
Activity 0 comments · opened Jul 25, 2026

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_TOKEN or ANTHROPIC_API_KEY set; default config dir (~/.claude); no apiKeyHelper / forceLoginMethod in 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/profile with the Keychain access token as Bearer returns Account B's profile and org).
  • But every identity surface still reports Account A:
  • oauthAccount in ~/.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 accountUuid stamped on cachedUsageUtilization in ~/.claude.json
  • oauthAccount.profileFetchedAt keeps 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_at timestamps in cachedUsageUtilization match 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)

  1. Sign into Account A, use normally.
  2. /logout, then /login and authenticate as Account B.
  3. claude auth status → still reports Account A.
  4. Verify token owner: read the Keychain item and call /api/oauth/profile with 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.

View original on GitHub ↗