Multiple Claude Code Accounts, One Shared Setup
I use more than one Claude account. Claude Code can handle that cleanly with CLAUDE_CONFIG_DIR: point each launch at a different directory and each account gets its own login, sessions, projects, and runtime state.
That solves account isolation, but creates a second problem. A fresh profile is also a fresh Claude setup. My global instructions are gone. My skills are gone. So are my hooks, agents, output styles, and statusline. If I copy them into every profile, those copies drift the next time I change anything.
The setup I landed on is simple:
Keep account-owned state inside each profile. Symlink user-owned configuration back to one canonical
~/.claudedirectory.
Every account stays isolated, but every account still feels like mine.
Start with one config directory per account
I keep profiles under ~/.claude-profiles:
~/.claude-profiles/
├── profileA/
└── profileB/
Launch Claude Code by setting CLAUDE_CONFIG_DIR for that process:
CLAUDE_CONFIG_DIR="$HOME/.claude-profiles/profileA" claude
And for the other account:
CLAUDE_CONFIG_DIR="$HOME/.claude-profiles/profileB" claude
That works, but I do not want to remember which profile belongs to a project every time I open a terminal. I use direnv to make the project directory choose for me.
Install it with Homebrew:
brew install direnv
Then hook it into Zsh. I use the direnv plugin in Oh My Zsh:
plugins=(git direnv)
You can also use direnv’s native Zsh hook without Oh My Zsh:
eval "$(direnv hook zsh)"
In a project that should use profileA, create .envrc in the project root:
export CLAUDE_CONFIG_DIR="$HOME/.claude-profiles/profileA"
Use the other profile in another project:
export CLAUDE_CONFIG_DIR="$HOME/.claude-profiles/profileB"
Approve each file once:
direnv allow
Now cd is the account switcher. Enter the first project and direnv exports profileA; leave it and direnv unloads that value; enter the other project and it exports profileB. Running claude normally always opens the account assigned to the current project.
Log into each profile once. From then on, each directory maintains its own account identity and runtime data.
Decide what belongs to the account and what belongs to you
The important part is not symlinking the entire directory. That would defeat the isolation we created.
I leave account identity and runtime state inside each profile:
~/.claude-profiles/profileA/
├── .claude.json
├── history.jsonl
├── backups/
├── cache/
├── plugins/
├── projects/
└── sessions/
The things that describe how I want Claude Code to work stay in the canonical directory:
~/.claude/
├── CLAUDE.md
├── settings.json
├── statusline.sh
├── agents/
├── commands/
├── hooks/
├── output-styles/
└── skills/
That distinction is the whole design. Account identity stays local to the profile. Personal configuration points back to ~/.claude.
Link the shared configuration
Here is the generic version of the script I use:
profiles=(profileA profileB)
shared_items=(
CLAUDE.md
settings.json
statusline.sh
skills
agents
commands
hooks
output-styles
)
for profile in "${profiles[@]}"; do
profile_root="$HOME/.claude-profiles/$profile"
mkdir -p "$profile_root"
for item in "${shared_items[@]}"; do
ln -s "$HOME/.claude/$item" "$profile_root/$item"
done
done
Afterward, a profile looks like this:
~/.claude-profiles/profileA/
├── .claude.json
├── projects/
├── sessions/
├── CLAUDE.md ───────> ~/.claude/CLAUDE.md
├── settings.json ───> ~/.claude/settings.json
├── statusline.sh ───> ~/.claude/statusline.sh
├── skills ──────────> ~/.claude/skills
├── agents ──────────> ~/.claude/agents
├── commands ────────> ~/.claude/commands
├── hooks ───────────> ~/.claude/hooks
└── output-styles ───> ~/.claude/output-styles
Change a skill once in ~/.claude/skills and both accounts see it immediately. Update the global CLAUDE.md and the rule applies everywhere. There is no sync command because there are no copies to synchronize.
Preserve anything already in a profile
If a profile already has its own settings.json or skills directory, move it aside before creating the link:
mv "$HOME/.claude-profiles/profileA/settings.json" \
"$HOME/.claude-profiles/profileA/settings.json.profile-backup"
ln -s "$HOME/.claude/settings.json" \
"$HOME/.claude-profiles/profileA/settings.json"
This also forces a real decision: should that setting vary by account? I wanted the same settings everywhere, so one shared file was correct. If you need a different model or permission mode per account, keep settings.json profile-specific and sync only the other paths.
The Mackup trap
My dotfiles are backed up with Mackup, which I cover in Dotfiles for Developers — Part 2. My first instinct was to add the whole directory:
[configuration_files]
.claude-profiles
That is the wrong boundary for this layout. Mackup follows directory symlinks while copying. Backing up the whole profile can duplicate the shared skills, hooks, and agents into every account. A restore can then leave real directories where the shared links are supposed to be.
Instead, I back up the shared setup through the normal Claude Code definition and list only account-owned identity files in the profiles definition:
[application]
name = Claude Code Profiles
[configuration_files]
.claude-profiles/profileA/.claude.json
.claude-profiles/profileB/.claude.json
My regular Claude Code definition owns the durable shared files:
[application]
name = Claude Code
[configuration_files]
.claude/CLAUDE.md
.claude/settings.json
.claude/statusline.sh
.claude/agents
.claude/commands
.claude/hooks
.claude/output-styles
.claude/skills
Now Mackup has the same boundary as Claude Code:
- the Claude definition backs up personal configuration once
- the profiles definition backs up account identity separately
- profile runtime junk is not copied into the dotfiles repository
Repair the links after a restore
Mackup does not have a post-restore hook, so I put a tiny wrapper named mackup earlier on my PATH. It calls the real binary, then runs an idempotent link-repair script:
#!/bin/zsh
set -eu
real_mackup="/opt/homebrew/bin/mackup"
sync_profile_links="$HOME/.dotfiles/mackup/bin/sync-claude-profile-links"
"$real_mackup" "$@"
"$sync_profile_links"
The repair script checks every expected link. If a restore or an old backup left a real file in its place, the script preserves that path with a timestamp before recreating the link:
if [[ -e "$target_path" || -L "$target_path" ]]; then
timestamp="$(date +%Y%m%d%H%M%S)"
mv "$target_path" "${target_path}.pre-shared-link-${timestamp}"
fi
ln -s "$source_path" "$target_path"
Because my dotfiles bin directory comes before Homebrew on PATH, normal commands still work:
mackup backup --force
mackup restore
The only difference is that the shared profile topology is checked and repaired after Mackup finishes.
The final mental model
I spent too long thinking of each profile as a complete Claude Code installation. It is not. A profile is only the state that must differ between accounts.
~/.claude remains the source of truth for how I work. ~/.claude-profiles/profileA and profileB only hold who I am logged in as and what happened while using that account.
Once that boundary is explicit, the setup stops being fragile:
- logging into one account cannot overwrite another
- changing a skill or hook updates every profile immediately
- the statusline and global rules stay consistent
- Mackup backs up each piece exactly once
- restore recreates the same layout instead of materializing duplicate directories
Separate identity, shared workflow. That is all multiple Claude Code profiles needed to be manageable.