docs: sync KB — claude-code-multi-account.md
All checks were successful
Reindex Knowledge Base / reindex (push) Successful in 4s
All checks were successful
Reindex Knowledge Base / reindex (push) Successful in 4s
This commit is contained in:
parent
4ecf93a3e2
commit
b7ed0f8435
224
workstation/claude-code-multi-account.md
Normal file
224
workstation/claude-code-multi-account.md
Normal file
@ -0,0 +1,224 @@
|
||||
---
|
||||
title: "Claude Code Multi-Account Setup with CLAUDE_CONFIG_DIR"
|
||||
description: "Guide to running multiple Claude Code OAuth accounts simultaneously using CLAUDE_CONFIG_DIR, direnv, and symlinked config directories."
|
||||
type: guide
|
||||
domain: workstation
|
||||
tags: [claude-code, oauth, direnv, config, multi-account]
|
||||
---
|
||||
|
||||
# Claude Code Multi-Account Setup
|
||||
|
||||
Run two different Claude OAuth accounts simultaneously — one for specific projects, another for everything else — using the `CLAUDE_CONFIG_DIR` environment variable and direnv.
|
||||
|
||||
## How CLAUDE_CONFIG_DIR Works
|
||||
|
||||
`CLAUDE_CONFIG_DIR` is an undocumented but fully implemented environment variable in the Claude Code binary. It controls where Claude Code looks for its entire configuration directory.
|
||||
|
||||
### Behavior When Set
|
||||
|
||||
| Aspect | Default | With CLAUDE_CONFIG_DIR |
|
||||
|--------|---------|----------------------|
|
||||
| Config directory | `~/.claude` | `$CLAUDE_CONFIG_DIR` |
|
||||
| Global state file | `~/.claude.json` | `$CLAUDE_CONFIG_DIR/.config.json` |
|
||||
| OAuth credentials | `~/.claude/.credentials.json` | `$CLAUDE_CONFIG_DIR/.credentials.json` |
|
||||
| Keychain entry name | `Claude Code` | `Claude Code-<hash>` (8-char hash of config path) |
|
||||
|
||||
### Internal Implementation
|
||||
|
||||
From the Claude Code binary source:
|
||||
|
||||
**Config dir resolution:**
|
||||
```js
|
||||
process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude")
|
||||
```
|
||||
|
||||
**Global state file (`.config.json` fallback):**
|
||||
```js
|
||||
// When CLAUDE_CONFIG_DIR is set, checks for .config.json first
|
||||
if (existsSync(path.join(configDir, ".config.json")))
|
||||
return path.join(configDir, ".config.json");
|
||||
```
|
||||
|
||||
**Keychain collision avoidance:**
|
||||
```js
|
||||
// Adds hash suffix when CLAUDE_CONFIG_DIR is set
|
||||
let suffix = !process.env.CLAUDE_CONFIG_DIR ? ""
|
||||
: `-${hash(configDir).substring(0, 8)}`;
|
||||
return `Claude Code${suffix}`;
|
||||
```
|
||||
|
||||
**Propagated to subagents:** `CLAUDE_CONFIG_DIR` is in the explicit list of env vars passed to child processes (subagents, background agents), ensuring they use the same config directory.
|
||||
|
||||
## Setup Procedure
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Claude Code installed (native binary at `~/.local/bin/claude`)
|
||||
- direnv (`sudo dnf install direnv` on Fedora/Nobara)
|
||||
|
||||
### 1. Create Alternate Config Directory
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.claude-ac
|
||||
```
|
||||
|
||||
The `-ac` suffix stands for "alternate config" — name it whatever you like.
|
||||
|
||||
### 2. Symlink Shared Config Files
|
||||
|
||||
Share configuration between accounts so both get the same CLAUDE.md, settings, MCP servers, hooks, etc.:
|
||||
|
||||
```bash
|
||||
# Core config files
|
||||
ln -s ~/.claude/CLAUDE.md ~/.claude-ac/CLAUDE.md
|
||||
ln -s ~/.claude/settings.json ~/.claude-ac/settings.json
|
||||
ln -s ~/.claude/command-permissions.json ~/.claude-ac/command-permissions.json
|
||||
|
||||
# Directories (agents, commands, hooks, skills, patterns, memory, plugins)
|
||||
for dir in agents commands hooks skills patterns memory plugins; do
|
||||
ln -s ~/.claude/$dir ~/.claude-ac/$dir
|
||||
done
|
||||
|
||||
# MCP servers + global state
|
||||
# When CLAUDE_CONFIG_DIR is set, Claude reads .config.json instead of ~/.claude.json
|
||||
ln -s ~/.claude.json ~/.claude-ac/.config.json
|
||||
```
|
||||
|
||||
#### What NOT to Symlink
|
||||
|
||||
These should remain independent per account:
|
||||
|
||||
| Path | Why independent |
|
||||
|------|----------------|
|
||||
| `.credentials.json` | Different OAuth tokens per account |
|
||||
| `projects/` | Session state tied to account |
|
||||
| `sessions/` | Active session registry |
|
||||
| `history.jsonl` | Conversation history |
|
||||
| `todos/`, `tasks/`, `plans/` | Conversation-scoped data |
|
||||
| `debug/`, `telemetry/`, `logs/` | Per-instance diagnostics |
|
||||
|
||||
These directories are created automatically by Claude Code on first use — no need to pre-create them.
|
||||
|
||||
### 3. Hook direnv Into Your Shell
|
||||
|
||||
**Fish** (`~/.config/fish/config.fish`, inside `if status is-interactive`):
|
||||
```fish
|
||||
direnv hook fish | source
|
||||
```
|
||||
|
||||
**Bash** (`~/.bashrc`):
|
||||
```bash
|
||||
eval "$(direnv hook bash)"
|
||||
```
|
||||
|
||||
### 4. Create `.envrc` for the Target Directory
|
||||
|
||||
For example, to use the alternate account in `~/work`:
|
||||
|
||||
```bash
|
||||
# ~/work/.envrc
|
||||
export CLAUDE_CONFIG_DIR="$HOME/.claude-ac"
|
||||
```
|
||||
|
||||
Allow it:
|
||||
```bash
|
||||
direnv allow ~/work/.envrc
|
||||
```
|
||||
|
||||
direnv automatically sets/unsets the env var when you `cd` into or out of `~/work` (and all subdirectories).
|
||||
|
||||
### 5. Log In With the Second Account
|
||||
|
||||
From within the target directory (where direnv activates):
|
||||
|
||||
```bash
|
||||
cd ~/work
|
||||
claude auth login
|
||||
```
|
||||
|
||||
This stores OAuth tokens in `~/.claude-ac/.credentials.json`, completely separate from the primary account.
|
||||
|
||||
### 6. Verify
|
||||
|
||||
```bash
|
||||
# In ~/work — should show alternate account
|
||||
cd ~/work && claude auth status
|
||||
|
||||
# Outside ~/work — should show primary account
|
||||
cd ~ && claude auth status
|
||||
```
|
||||
|
||||
Both accounts can run simultaneously in separate terminal windows.
|
||||
|
||||
## Current Configuration on This Workstation
|
||||
|
||||
| Location | Account | Purpose |
|
||||
|----------|---------|---------|
|
||||
| `~/.claude` | Primary (cal.corum@gmail.com) | All projects except ~/work |
|
||||
| `~/.claude-ac` | Alternate | ~/work projects |
|
||||
| `~/work/.envrc` | — | direnv trigger for CLAUDE_CONFIG_DIR |
|
||||
|
||||
## How It All Fits Together
|
||||
|
||||
```
|
||||
Terminal in ~/work/some-project/
|
||||
↓ cd triggers direnv
|
||||
↓ CLAUDE_CONFIG_DIR=~/.claude-ac
|
||||
↓ claude starts
|
||||
├── Config dir: ~/.claude-ac/
|
||||
├── Auth: ~/.claude-ac/.credentials.json (alt account)
|
||||
├── Settings: ~/.claude-ac/settings.json → symlink → ~/.claude/settings.json
|
||||
├── MCP servers: ~/.claude-ac/.config.json → symlink → ~/.claude.json
|
||||
├── Hooks: ~/.claude-ac/hooks/ → symlink → ~/.claude/hooks/
|
||||
└── Keychain: "Claude Code-a1b2c3d4" (hashed, no collision)
|
||||
|
||||
Terminal in ~/other-project/
|
||||
↓ no .envrc, CLAUDE_CONFIG_DIR unset
|
||||
↓ claude starts
|
||||
├── Config dir: ~/.claude/ (default)
|
||||
├── Auth: ~/.claude/.credentials.json (primary account)
|
||||
└── Keychain: "Claude Code" (default)
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Auth shows the wrong account
|
||||
Check that direnv loaded correctly:
|
||||
```bash
|
||||
echo $CLAUDE_CONFIG_DIR
|
||||
# Should show ~/.claude-ac when in ~/work, empty otherwise
|
||||
```
|
||||
|
||||
If empty, ensure:
|
||||
1. direnv hook is in your shell config
|
||||
2. You opened a new shell after adding the hook
|
||||
3. `direnv allow ~/work/.envrc` was run
|
||||
|
||||
### MCP servers not loading
|
||||
Verify the `.config.json` symlink:
|
||||
```bash
|
||||
ls -la ~/.claude-ac/.config.json
|
||||
# Should point to ~/.claude.json
|
||||
```
|
||||
|
||||
### direnv doesn't activate in subdirectories
|
||||
direnv walks up the directory tree, so `~/work/.envrc` covers all of `~/work/**`. If it doesn't activate:
|
||||
```bash
|
||||
direnv status # Shows which .envrc is loaded and why
|
||||
```
|
||||
|
||||
### Need to re-login
|
||||
```bash
|
||||
cd ~/work # Ensure direnv sets the env var
|
||||
claude auth logout
|
||||
claude auth login
|
||||
```
|
||||
|
||||
### Subagents use wrong account
|
||||
`CLAUDE_CONFIG_DIR` is in Claude Code's propagated env var list, so subagents inherit it automatically. If a subagent somehow uses the wrong account, verify the parent process has `CLAUDE_CONFIG_DIR` set.
|
||||
|
||||
## Caveats
|
||||
|
||||
- **Undocumented feature**: `CLAUDE_CONFIG_DIR` does not appear in `claude --help` or official docs. However, it is deeply integrated into the binary (config resolution, keychain naming, subagent propagation), suggesting it's intentional infrastructure.
|
||||
- **Version sensitivity**: When Claude Code updates add new shared config files to `~/.claude/`, the alternate config directory won't automatically get symlinks for them. Periodically check for new files that should be symlinked.
|
||||
- **Session isolation**: Even with symlinked memory, session history and project state are per-config-dir. Each account maintains its own conversation history.
|
||||
Loading…
Reference in New Issue
Block a user