7.3 KiB
| title | description | type | domain | tags | |||||
|---|---|---|---|---|---|---|---|---|---|
| Claude Code Multi-Account Setup with CLAUDE_CONFIG_DIR | Guide to running multiple Claude Code OAuth accounts simultaneously using CLAUDE_CONFIG_DIR, direnv, and symlinked config directories. | guide | workstation |
|
Claude Code Multi-Account Setup
Run two different Claude OAuth accounts simultaneously — one for specific projects, another for everything else — using the CLAUDE_CONFIG_DIR environment variable and direnv.
How CLAUDE_CONFIG_DIR Works
CLAUDE_CONFIG_DIR is an undocumented but fully implemented environment variable in the Claude Code binary. It controls where Claude Code looks for its entire configuration directory.
Behavior When Set
| Aspect | Default | With CLAUDE_CONFIG_DIR |
|---|---|---|
| Config directory | ~/.claude |
$CLAUDE_CONFIG_DIR |
| Global state file | ~/.claude.json |
$CLAUDE_CONFIG_DIR/.config.json |
| OAuth credentials | ~/.claude/.credentials.json |
$CLAUDE_CONFIG_DIR/.credentials.json |
| Keychain entry name | Claude Code |
Claude Code-<hash> (8-char hash of config path) |
Internal Implementation
From the Claude Code binary source:
Config dir resolution:
process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude")
Global state file (.config.json fallback):
// When CLAUDE_CONFIG_DIR is set, checks for .config.json first
if (existsSync(path.join(configDir, ".config.json")))
return path.join(configDir, ".config.json");
Keychain collision avoidance:
// Adds hash suffix when CLAUDE_CONFIG_DIR is set
let suffix = !process.env.CLAUDE_CONFIG_DIR ? ""
: `-${hash(configDir).substring(0, 8)}`;
return `Claude Code${suffix}`;
Propagated to subagents: CLAUDE_CONFIG_DIR is in the explicit list of env vars passed to child processes (subagents, background agents), ensuring they use the same config directory.
Setup Procedure
Prerequisites
- Claude Code installed (native binary at
~/.local/bin/claude) - direnv (
sudo dnf install direnvon Fedora/Nobara)
1. Create Alternate Config Directory
mkdir -p ~/.claude-ac
The -ac suffix stands for "alternate config" — name it whatever you like.
2. Symlink Shared Config Files
Share configuration between accounts so both get the same CLAUDE.md, settings, MCP servers, hooks, etc.:
# Core config files
ln -s ~/.claude/CLAUDE.md ~/.claude-ac/CLAUDE.md
ln -s ~/.claude/settings.json ~/.claude-ac/settings.json
ln -s ~/.claude/command-permissions.json ~/.claude-ac/command-permissions.json
# Directories (agents, commands, hooks, skills, patterns, memory, plugins)
for dir in agents commands hooks skills patterns memory plugins; do
ln -s ~/.claude/$dir ~/.claude-ac/$dir
done
# MCP servers + global state
# When CLAUDE_CONFIG_DIR is set, Claude reads .config.json instead of ~/.claude.json
ln -s ~/.claude.json ~/.claude-ac/.config.json
What NOT to Symlink
These should remain independent per account:
| Path | Why independent |
|---|---|
.credentials.json |
Different OAuth tokens per account |
projects/ |
Session state tied to account |
sessions/ |
Active session registry |
history.jsonl |
Conversation history |
todos/, tasks/, plans/ |
Conversation-scoped data |
debug/, telemetry/, logs/ |
Per-instance diagnostics |
These directories are created automatically by Claude Code on first use — no need to pre-create them.
3. Hook direnv Into Your Shell
Fish (~/.config/fish/config.fish, inside if status is-interactive):
direnv hook fish | source
Bash (~/.bashrc):
eval "$(direnv hook bash)"
4. Create .envrc for the Target Directory
For example, to use the alternate account in ~/work:
# ~/work/.envrc
export CLAUDE_CONFIG_DIR="$HOME/.claude-ac"
Allow it:
direnv allow ~/work/.envrc
direnv automatically sets/unsets the env var when you cd into or out of ~/work (and all subdirectories).
5. Log In With the Second Account
From within the target directory (where direnv activates):
cd ~/work
claude auth login
This stores OAuth tokens in ~/.claude-ac/.credentials.json, completely separate from the primary account.
6. Verify
# In ~/work — should show alternate account
cd ~/work && claude auth status
# Outside ~/work — should show primary account
cd ~ && claude auth status
Both accounts can run simultaneously in separate terminal windows.
Current Configuration on This Workstation
| Location | Account | Purpose |
|---|---|---|
~/.claude |
Primary (cal.corum@gmail.com) | All projects except ~/work |
~/.claude-ac |
Alternate | ~/work projects |
~/work/.envrc |
— | direnv trigger for CLAUDE_CONFIG_DIR |
How It All Fits Together
Terminal in ~/work/some-project/
↓ cd triggers direnv
↓ CLAUDE_CONFIG_DIR=~/.claude-ac
↓ claude starts
├── Config dir: ~/.claude-ac/
├── Auth: ~/.claude-ac/.credentials.json (alt account)
├── Settings: ~/.claude-ac/settings.json → symlink → ~/.claude/settings.json
├── MCP servers: ~/.claude-ac/.config.json → symlink → ~/.claude.json
├── Hooks: ~/.claude-ac/hooks/ → symlink → ~/.claude/hooks/
└── Keychain: "Claude Code-a1b2c3d4" (hashed, no collision)
Terminal in ~/other-project/
↓ no .envrc, CLAUDE_CONFIG_DIR unset
↓ claude starts
├── Config dir: ~/.claude/ (default)
├── Auth: ~/.claude/.credentials.json (primary account)
└── Keychain: "Claude Code" (default)
Troubleshooting
Auth shows the wrong account
Check that direnv loaded correctly:
echo $CLAUDE_CONFIG_DIR
# Should show ~/.claude-ac when in ~/work, empty otherwise
If empty, ensure:
- direnv hook is in your shell config
- You opened a new shell after adding the hook
direnv allow ~/work/.envrcwas run
MCP servers not loading
Verify the .config.json symlink:
ls -la ~/.claude-ac/.config.json
# Should point to ~/.claude.json
direnv doesn't activate in subdirectories
direnv walks up the directory tree, so ~/work/.envrc covers all of ~/work/**. If it doesn't activate:
direnv status # Shows which .envrc is loaded and why
Need to re-login
cd ~/work # Ensure direnv sets the env var
claude auth logout
claude auth login
Subagents use wrong account
CLAUDE_CONFIG_DIR is in Claude Code's propagated env var list, so subagents inherit it automatically. If a subagent somehow uses the wrong account, verify the parent process has CLAUDE_CONFIG_DIR set.
Caveats
- Undocumented feature:
CLAUDE_CONFIG_DIRdoes not appear inclaude --helpor official docs. However, it is deeply integrated into the binary (config resolution, keychain naming, subagent propagation), suggesting it's intentional infrastructure. - Version sensitivity: When Claude Code updates add new shared config files to
~/.claude/, the alternate config directory won't automatically get symlinks for them. Periodically check for new files that should be symlinked. - Session isolation: Even with symlinked memory, session history and project state are per-config-dir. Each account maintains its own conversation history.