---
ai_assisted: true
title: "Running Multiple Claude Code Accounts on One Machine"
description: "Run isolated Claude Code accounts on the same Mac with one env var and a small zsh function"
pubDate: 2026-05-20T17:01:28+09:00
category: "AI"
subcategory: "Agent"
tags: ["Claude Code", "Multi-account", "zsh", "Dotfiles", "Productivity", "Configuration", "Developer Workflow"]
heroImage: ""
series: "claude-multi-account"
seriesOrder: 1
seriesTitle: "Multi-Account Claude Code Operations"
serial: 11
draft: false
published: true
---

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:

```bash
mkdir ~/.claude-2
```

No 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   ← independent
```

The `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.

```zsh
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:

```bash
# 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 -2cy
```

To 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:

```bash
# 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/plugins
```

After 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 permissions
- `CLAUDE.md` — project memory and instructions
- `commands/` — custom slash commands
- `hooks/` — pre/post-tool hooks
- `history.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 — `~/.zshrc` and 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:

```bash
# ~/.claude-2.env
export ANTHROPIC_API_KEY="sk-ant-..."
```

```bash
# In your shell function or a wrapper alias
source ~/.claude-2.env && claude -2
```

**Shell 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:

```bash
echo $CLAUDE_CONFIG_DIR
```

from 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).
