Running Multiple Claude Profiles from One Machine

Claude Code stores its configuration in ~/.claude by default. One directory, one identity, one set of preferences. That works fine until you’re using Claude for both work and personal projects, and you’d rather keep them separate: different API keys, different global instructions, different memory, different MCP servers.

The fix is one environment variable and a shell alias.

The problem

Claude Code reads its config from a single directory. If you work at a company that has a corporate Claude plan and you also run personal side projects, everything lands in the same place. Your work CLAUDE.md bleeds into your hobby projects. Your personal memory accumulates alongside your professional one. If your employer routes billing through their own API key, you can’t easily switch to a personal key for side work without touching the config each time.

It gets worse if you maintain separate personas or writing styles for different contexts. A global CLAUDE.md that says “always use camelCase” will follow you into a project where you want snake_case.

CLAUDE_CONFIG_DIR

Claude Code respects a CLAUDE_CONFIG_DIR environment variable. Set it, and Claude reads and writes all its persistent state from that directory instead of ~/.claude. Sessions, memory, global instructions, API keys, MCP server config: all of it.

CLAUDE_CONFIG_DIR=~/.claude-personal claude

That’s the whole mechanism. You can create as many config directories as you want and switch between them by setting the variable.

To set up a second profile from scratch, just point the variable at a new directory and run Claude. It’ll initialize the directory on first launch.

mkdir -p ~/.claude-personal
CLAUDE_CONFIG_DIR=~/.claude-personal claude
# Claude will ask you to log in and run through first-time setup

Each directory is fully independent. Different API key, different CLAUDE.md, different conversation history, different MCP servers. No cross-contamination.

Shell aliases

Typing CLAUDE_CONFIG_DIR=~/.claude-personal claude every time is annoying. An alias handles it:

# ~/.bashrc or ~/.bash_profile
alias claude-personal='CLAUDE_CONFIG_DIR=~/.claude-personal command claude'

The command prefix matters. Without it, the alias would call itself and loop. command claude bypasses aliases and functions and hits the binary directly.

After reloading your shell (source ~/.bashrc), claude uses your work profile and claude-personal uses the personal one.

Preventing accidental cross-contamination

The alias solves the “I forgot which profile I wanted” problem, but you can go further. If a directory is a personal project, mark it with an empty .claude-personal file and add a guard to your shell that refuses to run the wrong claude there.

# In ~/.bashrc, replace the plain `claude` with a function
claude() {
    local dir="$PWD"
    while [[ "$dir" != "/" ]]; do
        if [[ -f "$dir/.claude-personal" ]]; then
            echo "Error: .claude-personal found in $dir — use 'claude-personal' instead." >&2
            return 1
        fi
        dir="$(dirname "$dir")"
    done
    command claude "$@"
}

alias claude-personal='CLAUDE_CONFIG_DIR=~/.claude-personal command claude'

The function walks up the directory tree (same logic git uses to find .git), so it fires whether you’re in the project root or a subdirectory. Running claude inside a personal project now fails with a clear message. claude-personal still works fine because the alias calls command claude, bypassing the function entirely.

Add .claude-personal to your global .gitignore if you don’t want to commit it to every repo.

Fish shell

# ~/.config/fish/config.fish

function claude
    set dir (pwd)
    while test "$dir" != "/"
        if test -f "$dir/.claude-personal"
            echo "Error: .claude-personal found in $dir — use claude-personal instead." >&2
            return 1
        end
        set dir (dirname $dir)
    end
    command claude $argv
end

alias claude-personal='CLAUDE_CONFIG_DIR=~/.claude-personal command claude'

Zsh

Zsh function syntax is identical to Bash, so the same block works. Drop it in ~/.zshrc:

claude() {
    local dir="$PWD"
    while [[ "$dir" != "/" ]]; do
        if [[ -f "$dir/.claude-personal" ]]; then
            echo "Error: .claude-personal found in $dir — use 'claude-personal' instead." >&2
            return 1
        fi
        dir="$(dirname "$dir")"
    done
    command claude "$@"
}

alias claude-personal='CLAUDE_CONFIG_DIR=~/.claude-personal command claude'

The ~/.claude.json file

There’s one file that sits outside the config directory: ~/.claude.json in your home folder. Claude writes it on first launch and uses it to store your login session. It belongs to whichever profile ran first without an env variable set. That’s typically your work profile, since that’s usually the default claude invocation.

When you use CLAUDE_CONFIG_DIR, Claude writes that profile’s .claude.json inside the pointed-to directory instead. So ~/.claude-personal/.claude.json holds your personal login, and ~/.claude.json holds the work one. No file is shared, no session leaks between profiles.

If you ran work Claude first (which most people do), this is already fine. The default claude command reads ~/.claude.json and the personal alias reads ~/.claude-personal/.claude.json. Nothing to do.

The only case that requires manual action is if you later decide to also move your work profile under an env variable (say, pointing claude at ~/.claude-work with a .claude-work guard file). At that point ~/.claude.json is stranded: it belongs to the work profile but isn’t inside ~/.claude-work. You’ll need to move it:

mv ~/.claude.json ~/.claude-work/.claude.json

Without that move, Claude will ask you to log in again on the next work session.

What lives in each profile

Each config directory is a full Claude installation. The things you’d typically configure per profile:

  • CLAUDE.md (global instructions): your work profile might have coding style rules and company context, your personal profile a different writing style and conventions.
  • Memory: Claude builds it up across sessions. Weekend project notes shouldn’t leak into your work context.
  • API key: if your employer pays for work usage but you want separate billing for personal projects, each profile handles its own.
  • MCP servers: work might need Jira or Confluence. Personal might need something else entirely.

Confirming which profile is active

You can always check by looking at the config directory Claude is using:

echo ${CLAUDE_CONFIG_DIR:-~/.claude}

Or just ask Claude mid-session: it knows where it’s reading its config from and will tell you if you ask.

If you use a custom statusline script, the profile is even easier to surface. Claude spawns the script as a child process, so it inherits CLAUDE_CONFIG_DIR from the environment. You can read it directly:

profile=""
case "${CLAUDE_CONFIG_DIR}" in
  *claude-personal*) profile=" [personal]" ;;
  *)                 profile=" [default]" ;;
esac
out="$out$profile"

No JSON payload field needed. The variable is just there.

Leave a Reply

Your email address will not be published. Required fields are marked *