You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
CLAUDE_CONFIG_DIR correctly isolates config files (.claude.json, settings, history) into separate directories, enabling multi-account workflows. However, on macOS, all Claude Code processes — regardless of which CLAUDE_CONFIG_DIR they use — read from and write to the same Keychain entry: "Claude Code-credentials".
This means:
Concurrent sessions overwrite each other's tokens. When two terminal sessions run with different CLAUDE_CONFIG_DIR values (pointing to different accounts), whichever session last performs an OAuth refresh writes its new token to the shared Keychain entry. The other session's in-memory token is now stale, and its next refresh will fail (see also [BUG] OAuth refresh returns 400 after early 401 before local expiresAt; concurrent sessions forced to /login #54443).
Rate limits are not account-isolated. Both sessions authenticate as whichever account last wrote to the Keychain, so usage and rate limits are attributed to that account regardless of which CLAUDE_CONFIG_DIR is set.
The Linux/WSL2 equivalent (credential path not scoped to CLAUDE_CONFIG_DIR) was tracked in #47661. This is the macOS counterpart, where the credential store is the Keychain rather than a plaintext file.
Log in to account A in ~/.claude-work, log in to account B in ~/.claude-home.
Open two terminals. Run claude-work in one, claude-home in the other.
In the claude-work terminal, trigger an OAuth refresh (use Claude until the token expires or run a long session).
Observe: the claude-home session now authenticates as account A (the work account), not account B.
Confirm with: security find-generic-password -s "Claude Code-credentials" -w — there is only one entry, and it reflects whichever account last refreshed.
What should happen?
When CLAUDE_CONFIG_DIR is set, the Keychain service name should be scoped to that directory so that each config directory maintains an isolated credential entry. For example:
CLAUDE_CONFIG_DIR
Keychain service name
(unset / default)
Claude Code-credentials
~/.claude-work
Claude Code-credentials (~/.claude-work)
~/.claude-home
Claude Code-credentials (~/.claude-home)
This would give CLAUDE_CONFIG_DIR true isolation across all stored state — matching what it already does for configs and history.
Environment
Claude Code version: latest
Platform: macOS
OS version: macOS 26 (Tahoe) 26.4.1
Shell: zsh
Terminal: Terminal.app / iTerm2
Additional context
The community has built several account-management tools (CCM, claude-swap, CCSwitcher) specifically to work around this limitation, all of which manually save and restore the single shared Keychain entry. This confirms the demand for native isolation. A PR to CCM that adds CLAUDE_CONFIG_DIR awareness to credential lookup (upstream dr5hn/ccm#4) further illustrates the pattern.
Preflight checklist
What's wrong?
CLAUDE_CONFIG_DIRcorrectly isolates config files (.claude.json, settings, history) into separate directories, enabling multi-account workflows. However, on macOS, all Claude Code processes — regardless of whichCLAUDE_CONFIG_DIRthey use — read from and write to the same Keychain entry:"Claude Code-credentials".This means:
Concurrent sessions overwrite each other's tokens. When two terminal sessions run with different
CLAUDE_CONFIG_DIRvalues (pointing to different accounts), whichever session last performs an OAuth refresh writes its new token to the shared Keychain entry. The other session's in-memory token is now stale, and its next refresh will fail (see also [BUG] OAuth refresh returns 400 after early 401 before local expiresAt; concurrent sessions forced to /login #54443).Rate limits are not account-isolated. Both sessions authenticate as whichever account last wrote to the Keychain, so usage and rate limits are attributed to that account regardless of which
CLAUDE_CONFIG_DIRis set.The env var workaround is destructive. Using
CLAUDE_CODE_OAUTH_TOKENto bypass Keychain sharing deletes the Keychain entry on process exit (CLAUDE_CODE_OAUTH_TOKEN silently deletes macOS Keychain credentials on exit #37512), breaking every other session — including VS Code extensions and other terminals.The Linux/WSL2 equivalent (credential path not scoped to
CLAUDE_CONFIG_DIR) was tracked in #47661. This is the macOS counterpart, where the credential store is the Keychain rather than a plaintext file.Steps to reproduce
~/.claude-work, log in to account B in~/.claude-home.claude-workin one,claude-homein the other.claude-workterminal, trigger an OAuth refresh (use Claude until the token expires or run a long session).claude-homesession now authenticates as account A (the work account), not account B.security find-generic-password -s "Claude Code-credentials" -w— there is only one entry, and it reflects whichever account last refreshed.What should happen?
When
CLAUDE_CONFIG_DIRis set, the Keychain service name should be scoped to that directory so that each config directory maintains an isolated credential entry. For example:CLAUDE_CONFIG_DIRClaude Code-credentials~/.claude-workClaude Code-credentials (~/.claude-work)~/.claude-homeClaude Code-credentials (~/.claude-home)This would give
CLAUDE_CONFIG_DIRtrue isolation across all stored state — matching what it already does for configs and history.Environment
Additional context
The community has built several account-management tools (CCM, claude-swap, CCSwitcher) specifically to work around this limitation, all of which manually save and restore the single shared Keychain entry. This confirms the demand for native isolation. A PR to CCM that adds
CLAUDE_CONFIG_DIRawareness to credential lookup (upstream dr5hn/ccm#4) further illustrates the pattern.Related issues:
CLAUDE_CODE_OAUTH_TOKENenv var deletes shared Keychain entry on exit