[BUG] CLAUDE_CONFIG_DIR does not isolate macOS Keychain credentials per config directory

Resolved 💬 2 comments Opened Jun 25, 2026 by jesseclark Closed Jun 28, 2026

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:

  1. 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 #54443).
  1. 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.
  1. The env var workaround is destructive. Using CLAUDE_CODE_OAUTH_TOKEN to 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'
  1. Log in to account A in ~/.claude-work, log in to account B in ~/.claude-home.
  2. Open two terminals. Run claude-work in one, claude-home in the other.
  3. In the claude-work terminal, trigger an OAuth refresh (use Claude until the token expires or run a long session).
  4. Observe: the claude-home session now authenticates as account A (the work account), not account B.
  5. 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_TOKEN env var deletes shared Keychain entry on exit
  • #54443 — OAuth 400 failures from concurrent session token conflicts (open)

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗