[BUG] CLAUDE_CONFIG_DIR does not isolate macOS Keychain credentials per config directory
Preflight checklist
- [x] I have searched existing issues and this is not a duplicate (related but distinct from #47661 which is Linux/WSL2-only; #24317, #37512, and #54443 which address OAuth race conditions and env var side-effects rather than Keychain key derivation)
- [x] I am using the latest version of Claude Code
- [x] This is a single, focused bug report
---
What's wrong?
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_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 #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 (#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
# ~/.zshrc
alias claude-work='CLAUDE_CONFIG_DIR=$HOME/.claude-work claude'
alias claude-home='CLAUDE_CONFIG_DIR=$HOME/.claude-home claude'
- Log in to account A in
~/.claude-work, log in to account B in~/.claude-home. - Open two terminals. Run
claude-workin one,claude-homein the other. - In the
claude-workterminal, trigger an OAuth refresh (use Claude until the token expires or run a long session). - Observe: the
claude-homesession 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.
Related issues:
- #47661 — same root cause on Linux/WSL2 (closed as not planned)
- #24317 — OAuth refresh race condition in concurrent sessions
- #37512 —
CLAUDE_CODE_OAUTH_TOKENenv var deletes shared Keychain entry on exit - #54443 — OAuth 400 failures from concurrent session token conflicts (open)
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗