I hit my Claude Code usage limit mid-week. I have a second subscription, but switching accounts the naive way — /logout, /login, repeat — is miserable, and it turns out “switching accounts” in Claude Code silently means switching everything: skills, MCP servers, settings, hooks, memory, session history. All of it.
What I actually wanted: one brain, two wallets. Same skills, same MCP servers, same muscle memory — just a different subscription getting billed.
Here’s the setup that got me there. ~20 minutes, fully reversible.
Step 1: Two config dirs, two aliases
Claude Code keeps all its state in a config directory — ~/.claude by default — and honors a CLAUDE_CONFIG_DIR env var to point elsewhere. That’s the whole trick:
# ~/.zshrc
# Claude Code multi-account aliases
alias claude-work='CLAUDE_CONFIG_DIR=~/.claude claude'
alias claude-personal='CLAUDE_CONFIG_DIR=~/.claude-personal claude'
Enter fullscreen mode Exit fullscreen mode
mkdir ~/.claude-personal
source ~/.zshrc
claude-personal # prompts OAuth login for the second account — one time
Enter fullscreen mode Exit fullscreen mode
Credentials land in whichever dir is active. Bare claude still defaults to ~/.claude, so nothing about your existing workflow changes.
Gotcha #1: .claude.json moves when you set the env var
Claude Code’s user-level config file (~/.claude.json — MCP servers, folder trust, onboarding state) lives next to ~/.claude, not inside it. But when CLAUDE_CONFIG_DIR is set, it’s read from inside the config dir instead.
So claude-work — even though it points at the same ~/.claude you’ve always used — creates a fresh, empty ~/.claude/.claude.json and suddenly has zero MCP servers. Fix: symlink it to the master, since it’s the same account:
ln -s ~/.claude.json ~/.claude/.claude.json
Enter fullscreen mode Exit fullscreen mode
Now bare claude and claude-work are byte-for-byte identical.
Step 2: Share the brain
The second account starts as a blank slate. Since only billing should differ, symlink everything shareable from the main dir:
cd ~/.claude-personal
ln -s ~/.claude/skills skills
ln -s ~/.claude/CLAUDE.md CLAUDE.md
ln -s ~/.claude/settings.json settings.json # hooks, permissions, env
ln -s ~/.claude/settings.local.json settings.local.json
ln -s ~/.claude/plugins plugins
Enter fullscreen mode Exit fullscreen mode
One source of truth: add a skill or tweak a hook once, both accounts see it.
MCP servers need more care. They live in .claude.json, which also holds the account identity (oauthAccount) — so you can’t symlink the whole file across accounts. Merge just the mcpServers block:
jq --slurpfile mcp <(jq '.mcpServers' ~/.claude.json) \
'.mcpServers = $mcp[0]' ~/.claude-personal/.claude.json \
> /tmp/merged.json && mv /tmp/merged.json ~/.claude-personal/.claude.json
Enter fullscreen mode Exit fullscreen mode
Gotcha #2: --resume doesn’t know about accounts
Session transcripts live at <config-dir>/projects/<project>/<session-id>.jsonl. Resume a personal session under the work account and Claude just says the session doesn’t exist. Worse, anything that launches bare claude for you — crash-restore scripts, launchd jobs — always resumes against the default dir.
The fix is a tiny shim earlier in $PATH than the real binary. Every session ID exists in exactly one account’s tree, so ownership is the routing signal:
#!/bin/bash
# ~/.local/bin/claude — routes --resume to the account that owns the session
REAL=/opt/homebrew/bin/claude # wherever `command -v claude` pointed before
if [[ -z "$CLAUDE_CONFIG_DIR" ]]; then
id="" prev=""
for arg in "$@"; do
if [[ $prev == "--resume" || $prev == "-r" ]]; then id=$arg; break; fi
if [[ $arg == --resume=* ]]; then id=${arg#--resume=}; break; fi
prev=$arg
done
if [[ -n $id ]] && compgen -G "$HOME/.claude-personal/projects/*/$id.jsonl" >/dev/null; then
export CLAUDE_CONFIG_DIR="$HOME/.claude-personal"
fi
fi
exec "$REAL" "$@"
Enter fullscreen mode Exit fullscreen mode
Now claude --resume <id> just works, from anywhere, for either account. Explicit CLAUDE_CONFIG_DIR (your aliases) passes through untouched.
Bonus: moving a conversation between wallets
The whole reason I did this was running out of credits mid-conversation. Since a session is just a transcript file, “continue this work conversation on my personal account” is a copy + resume:
#!/bin/bash
# ~/.local/bin/claude-pickup <session-id> — continue a work session as personal
set -euo pipefail
id=${1:?usage: claude-pickup <session-id>}
src=$(ls ~/.claude/projects/*/"$id".jsonl 2>/dev/null | head -1)
[[ -n $src ]] || { echo "session $id not found" >&2; exit 1; }
proj=$(basename "$(dirname "$src")")
mkdir -p ~/.claude-personal/projects/"$proj"
cp -n "$src" ~/.claude-personal/projects/"$proj"/
CLAUDE_CONFIG_DIR=~/.claude-personal exec claude --resume "$id"
Enter fullscreen mode Exit fullscreen mode
The work account keeps its copy as history; new turns land only in the personal copy. And since the shim checks the personal tree first, future bare claude --resume of that ID follows the conversation to its new wallet automatically.
What stays separate (and should)
- Credentials and billing — the entire point.
- Session transcripts — the shim depends on separate trees.
- Folder trust — each account re-asks “do you trust this folder?” once per directory. Expected, answer once.
-
OAuth’d MCP servers (Linear, Notion, …) — tokens are per config dir; run
/mcponce in the new account to re-auth. CLI-based MCP servers that use machine credentials work immediately.
The result
claude # work (default, unchanged)
claude-personal # second subscription, same brain
claude --resume X # figures out the account itself
claude-pickup X # mid-conversation wallet swap
Enter fullscreen mode Exit fullscreen mode
Same skills, same MCP servers, same hooks, same memory. The only thing that changes is who gets the bill.
답글 남기기