Multi-Account Claude Code via CLAUDE_CONFIG_DIR
- 1. Multi-Account Claude Code via CLAUDE_CONFIG_DIR
- 2. Sharing Claude Code Sessions via Symlinked .jsonl
- 3. Claude Code Settings Sync and Troubleshooting
Claude Code has rate limits. Hit them often enough and you start paying for a second account. Add the desire to keep work projects under a company account and personal projects under a personal one, and you end up running multiple Claude Code accounts on the same machine.
The friction is that Claude Code defaults to a single config root at ~/.claude. Switching accounts means swapping config directories, juggling terminals logged into different accounts, or manually rotating API keys — none of it clean.
The CLAUDE_CONFIG_DIR environment variable lets a single machine run multiple Claude Code instances with fully independent settings, history, and sessions. The numbering convention (~/.claude, ~/.claude-2, ~/.claude-N) maps directly to flag arguments in a small shell function. This post covers the config directory layout, the shell-function definition, migration from a single-account setup, what each instance actually isolates, and a few operational notes worth knowing before you start.
What CLAUDE_CONFIG_DIR Does
By default, Claude Code stores all of its state — settings, conversation history, sessions, MCP configuration — in ~/.claude. When CLAUDE_CONFIG_DIR is set to a different path before invoking claude, the process uses that path as its root instead. The original directory is untouched.
This is the entire mechanism. There is no Claude Code setting, no login flow, no API-level account switching. One environment variable, one process, one directory. Run two terminals with different values of CLAUDE_CONFIG_DIR and you have two completely isolated Claude Code instances side by side.
Directory Layout and Naming
The convention used here assigns an integer index to each instance:
| Flag | Config directory |
|---|---|
-1 |
~/.claude (default, unchanged) |
-2 |
~/.claude-2 |
-3 |
~/.claude-3 |
-N |
~/.claude-N |
Create the second instance directory before first use:
mkdir ~/.claude-2No initialization beyond that is required. Claude Code creates its internal structure on first launch.
The directory layout for a two-instance setup:
~/.claude/ ← instance 1 (-1, default)
├── settings.json
├── CLAUDE.md
├── commands/
├── hooks/
├── plugins/ ← source
└── history.jsonl
~/.claude-2/ ← instance 2 (-2)
├── settings.json ← independent copy
├── CLAUDE.md ← independent copy
├── commands/ ← independent copy
├── hooks/ ← independent copy
├── plugins → ~/.claude/plugins ← symlink (shared)
└── history.jsonl ← independentThe plugins directory is symlinked rather than copied. A plugin installed in either instance becomes available to both without any manual sync step.
The Shell Function
The function below lives in ~/.oh-my-zsh/custom/aliases.zsh (or equivalent). It intercepts -1 and -2 flags before forwarding the remaining arguments to the real claude binary.
claude() {
local args=()
local config_dir=""
for arg in "$@"; do
if [[ "$arg" =~ ^-[ycg12]+$ ]]; then
local flags="${arg#-}"
for (( i=0; i<${#flags}; i++ )); do
case "${flags:$i:1}" in
1) config_dir="$HOME/.claude" ;;
2) config_dir="$HOME/.claude-2" ;;
esac
done
else
args+=("$arg")
fi
done
if [[ -n "$config_dir" ]]; then
CLAUDE_CONFIG_DIR="$config_dir" command claude "${args[@]}"
else
command claude "${args[@]}"
fi
}The regex ^-[ycg12]+$ matches combined short flags like -2y or -2cy. Each character is parsed individually, so flags can be stacked in any order. The command builtin bypasses the shell function and calls the actual binary, preventing infinite recursion.
Usage examples:
# Default instance (no flag — falls through to ~/.claude)
claude
# Explicitly select instance 1
claude -1
# Instance 2
claude -2
# Instance 2 with stacked flags (yolo + continue)
claude -2y
claude -2cyTo add a third instance, create ~/.claude-3, add a 3) config_dir="$HOME/.claude-3" case to the function, and update the regex to include 3.
Migrating from a Single-Account Setup
Existing ~/.claude users keep their setup as-is. That directory continues to be the default when no flag is given. New instances are created alongside it without touching the original.
To pre-populate a new instance with the same base settings rather than starting from scratch:
# Copy independent config files
cp ~/.claude/settings.json ~/.claude-2/
cp ~/.claude/CLAUDE.md ~/.claude-2/
cp -r ~/.claude/commands ~/.claude-2/
cp -r ~/.claude/hooks ~/.claude-2/
# Share plugins via symlink
ln -sf ~/.claude/plugins ~/.claude-2/pluginsAfter this copy, ~/.claude-2/settings.json and ~/.claude-2/CLAUDE.md are independent files. Edits in one instance do not propagate to the other. The plugins symlink is the deliberate exception — it keeps plugin state synchronized without duplication.
Permissions carry over from the copy. No ownership changes are needed on a single-user Mac.
What Gets Isolated, What Does Not
Isolated per instance:
settings.json— model selection, environment variables, tool permissionsCLAUDE.md— project memory and instructionscommands/— custom slash commandshooks/— pre/post-tool hookshistory.jsonl— full conversation history- Sessions — the active session list shown in the resume dialog
Shared or unaffected:
plugins/— when symlinked as described above- Global zsh aliases and functions —
~/.zshrcand your custom alias files load for every terminal regardless of which instance is active - System
PATH— binary resolution is identical across instances - OS-level state — keychain, Spotlight, launchd, anything outside
~/.claude-N
The isolation boundary is exactly the config directory. Nothing above it is affected.
Operational Notes
API key separation. CLAUDE_CODE_API_KEY or ANTHROPIC_API_KEY set in a shell session applies to whichever instance you launch from that session. If your instances need distinct keys — for example, a work subscription vs. a personal account — put them in per-instance env files and source the correct one before invoking claude -2. A simple pattern:
# ~/.claude-2.env
export ANTHROPIC_API_KEY="sk-ant-..."# In your shell function or a wrapper alias
source ~/.claude-2.env && claude -2Shell alias collisions. The claude() function wraps the binary name. Any other alias targeting claude in the same shell scope will conflict. Confirm with type claude that only one definition is active.
settings.json env block. Claude Code reads an env key from settings.json and injects those variables at startup. Instance-specific environment variables (API keys, base URLs, custom model overrides) can live there instead of in shell env files. Each instance's settings.json is independent, so the values stay scoped.
Confirming the active instance. There is no prompt-level indicator of which config directory is active. If you need to verify, run:
echo $CLAUDE_CONFIG_DIRfrom within the shell session that launched Claude Code. An empty value means the default ~/.claude is in use.
Adding more instances. The pattern scales linearly. A fourth instance is mkdir ~/.claude-4, a 4) case in the function, and a copy or fresh start of config files. There is no upper bound enforced by Claude Code.
Closing
Anyone running Claude Code with more than one account or configuration context on the same Mac can use this setup. One environment variable does the work; the shell function is the only glue needed to make it ergonomic.
Later posts in this series cover session sharing across instances (Ep2) and settings synchronization with sync-claude.sh (Ep3).