DEV Community

Daksh Gargas
Daksh Gargas

Posted on

One Brain, Two Wallets: Two Claude Code Accounts on One Machine

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 /mcp once 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.

Top comments (0)