---
ai_assisted: true
title: "Claude Code Settings Sync and Troubleshooting"
description: "Sync matrix, migration steps, common failures, and diagnostic commands for running multiple Claude Code accounts on one machine"
pubDate: 2026-05-20
category: "AI"
subcategory: "Agent"
tags: ["Claude Code", "Multi-account", "Settings Sync", "Troubleshooting", "Configuration", "MCP", "Developer Workflow"]
heroImage: ""
series: "claude-multi-account"
seriesOrder: 3
seriesTitle: "Multi-Account Claude Code Operations"
serial: 18
draft: false
published: true
---

Running multiple accounts adds a new kind of friction: settings drift. Install a plugin in one account and it's invisible in the others. Add a slash command, update a hook, configure an MCP server — any change made in one account has to be manually replicated elsewhere. The more accounts, the worse it gets.

Seamless multi-account use requires a clear boundary: some settings should be identical across all accounts, others should stay account-specific. Drawing that line and automating the sync is what this post covers.

Ep1 covered CLAUDE_CONFIG_DIR-based account isolation; Ep2 covered session sharing. This post adds the operational layer: what to share, what to keep isolated, how to migrate safely, and how to debug common failures.

---

## What needs syncing

Each account directory (`~/.claude`, `~/.claude-2`, `~/.claude-3`, `~/.claude-4`) is a fully independent config root. By default, changes made in one account — installing a plugin, adding a slash command, updating a hook — are invisible to all others. The sync script (`sync-claude.sh`) addresses this through bidirectional propagation: first pulling the newest version of each shared artifact into the primary instance, then pushing the reconciled state out to every account.

The following artifacts are sync candidates:

- **`settings.json`** — API keys, environment variables, hook definitions, model preferences, permission rules. Everything else degrades gracefully if it drifts; this file does not.
- **`CLAUDE.md`** — The user instruction document that shapes Claude's behavior across sessions. Any account where this diverges will behave differently.
- **`cwf-hooks-enabled.sh`** — Feature flag file for conditional hook activation. Shared so that enabling a feature in one account propagates to all.
- **`commands/`** — Slash command definitions. A command added via one account becomes available everywhere.
- **`hooks/`** — Hook scripts referenced by `settings.json`. The hook definitions in `settings.json` and the scripts in `hooks/` must stay aligned.
- **`skills/`**, **`agents/`**, **`hud/`** — OMC-layer extensions. Treated as shared by default.
- **`plugins/`** — Handled differently: not copied but symlinked. All accounts point to `~/.claude/plugins` as the physical source.

Two categories stay isolated regardless of sync configuration:

- **Session history (`.jsonl` files in `projects/`)** — Handled separately via the shared projects symlink described in Ep2. The sync script does not touch these.
- **`memory/`** — Per-account memory is independent by default. Cross-account memory sharing requires an explicit symlink step, described in the troubleshooting section below.

---

## Share-vs-isolate matrix

| Artifact | Sync behavior | Direction | Notes |
|---|---|---|---|
| `settings.json` | Synced | Bidirectional | `.claude-3` receives a filtered copy (see below) |
| `CLAUDE.md` | Synced | Bidirectional | Full copy |
| `cwf-hooks-enabled.sh` | Synced | Bidirectional | Full copy |
| `commands/` | Synced | Bidirectional | `rsync --delete` in forward pass |
| `hooks/` | Synced | Bidirectional | `rsync --delete` in forward pass |
| `skills/` | Synced | Bidirectional | `rsync --delete` in forward pass |
| `agents/` | Synced | Bidirectional | `rsync --delete` in forward pass |
| `hud/` | Synced | Bidirectional | `rsync --delete` in forward pass |
| `plugins/` | Symlinked | N/A | All accounts point to `~/.claude/plugins` |
| `projects/` (sessions) | Not touched | N/A | Managed by Ep2 symlink |
| `memory/` | Isolated | N/A | Opt-in symlink only |
| `history.jsonl` | Isolated | N/A | Per-account, never synced |

### The `.claude-3` exception

`.claude-3` is treated as a tracing/telemetry instance. When the forward sync pass writes `settings.json` to it, the script applies a `jq` filter that removes two categories of content before writing:

`OTEL_*` environment variables — the tracing instance maintains its own OpenTelemetry configuration and must not inherit the primary instance's values.

Hook commands containing `sync` or `cctrace` in their command string — these are instance-specific behaviors that should not propagate to the tracing account.

The filter applied:

```bash
jq '
  .env |= with_entries(select(.key | startswith("OTEL_") | not)) |
  if .hooks then
    .hooks |= with_entries(
      .value |= map(
        .hooks |= map(select(.command | (test("sync"; "i") or test("cctrace"; "i")) | not))
      )
    )
  else . end
' ~/.claude/settings.json > ~/.claude-3/settings.json
```

The consequence: `.claude-3/settings.json` is always overwritten by the forward sync pass. Any manual edit to that file will be lost the next time the script runs. If you need a persistent setting in `.claude-3`, make it in `~/.claude/settings.json` at the source, not in the destination.

---

## Migration steps

### From single account to multi-account with sync

If you are starting from a single `~/.claude` and want to add `~/.claude-2`:

```bash
# 1. Create the second account directory
mkdir ~/.claude-2

# 2. Run Claude once with the new account to let it initialize
CLAUDE_CONFIG_DIR=~/.claude-2 claude --version

# 3. Run sync to propagate primary settings to the new account
bash ~/.claude/scripts/sync-claude.sh
```

The sync script's Phase 2 (forward pass) will copy `settings.json`, `CLAUDE.md`, all command/hook/skill directories, and create the `plugins` symlink in `~/.claude-2`. The new account starts with a full copy of your primary configuration.

### Adding sync to an existing multi-account setup

If `~/.claude-2` already has its own `settings.json` that differs from `~/.claude`:

```bash
# 1. Back up both before anything runs
cp ~/.claude/settings.json ~/.claude/settings.json.bak
cp ~/.claude-2/settings.json ~/.claude-2/settings.json.bak

# 2. Run sync — Phase 1 pulls newer files from secondary accounts
#    into primary before Phase 2 pushes primary out to all accounts
bash ~/.claude/scripts/sync-claude.sh

# 3. Verify the merged result looks correct
cat ~/.claude/settings.json
```

Phase 1 uses `rsync --update`, which copies a file from a secondary account to the primary only if the secondary's version is newer by modification timestamp. If both files have the same timestamp (common when copied manually), neither overwrites the other. In that case, manually reconcile the two `settings.json` files before running the script.

### Rollback order

If sync produces an unexpected result:

```bash
# Restore primary from backup
cp ~/.claude/settings.json.bak ~/.claude/settings.json

# Restore secondary accounts
cp ~/.claude-2/settings.json.bak ~/.claude-2/settings.json
cp ~/.claude-3/settings.json.bak ~/.claude-3/settings.json

# Plugins symlinks: if broken, recreate manually
rm ~/.claude-2/plugins ~/.claude-3/plugins ~/.claude-4/plugins
ln -sf ~/.claude/plugins ~/.claude-2/plugins
ln -sf ~/.claude/plugins ~/.claude-3/plugins
ln -sf ~/.claude/plugins ~/.claude-4/plugins
```

Always back up before running the script in an environment where `settings.json` files have diverged significantly. The `--update` flag protects against overwriting newer files with older ones, but it cannot protect against timestamp collisions.

---

## Common failures

### Broken plugins symlink

**Symptom:** Commands from a plugin that works in `~/.claude` are unavailable in `~/.claude-2`. Running `ls -la ~/.claude-2/plugins` shows a broken symlink or a plain directory.

**Cause:** The plugins path in the secondary account was replaced with a directory (e.g., by a manual `cp -r`), or the symlink target moved.

**Fix:**

```bash
rm -rf ~/.claude-2/plugins
ln -sf ~/.claude/plugins ~/.claude-2/plugins
```

Repeat for each affected account. The sync script recreates the symlink on every forward pass, so the next sync will also fix this automatically.

### `settings.json` parse error

**Symptom:** Claude Code fails to start in an account, or starts with no hooks/permissions active. The terminal may show a JSON parse error.

**Cause:** A partial write left `settings.json` in a malformed state — common when the `jq` filter for `.claude-3` is interrupted mid-write, or when a manual edit introduced a trailing comma or mismatched brace.

**Fix:**

```bash
# Validate syntax
cat ~/.claude-2/settings.json | python3 -m json.tool > /dev/null

# If invalid, restore from primary or backup
cp ~/.claude/settings.json ~/.claude-2/settings.json
```

For `.claude-3`, do not copy directly from primary — run the sync script instead so the filter is applied correctly.

### Hook conflict between accounts

**Symptom:** A hook runs in the primary account but silently does nothing in a secondary, or produces unexpected behavior because the hook script path is wrong.

**Cause:** The `settings.json` hook definition references an absolute path (e.g., `/Users/you/.claude/hooks/SessionEnd/sync-step-1`) that exists in the primary account's `hooks/` directory but was not synced to the secondary, or was synced but the `settings.json` still points to the primary path.

**Fix:** Use `~/.claude/hooks/` as the path in hook definitions if the hooks directory is synced — the secondary account will have the same scripts under `~/.claude-2/hooks/`, but if `settings.json` hard-codes `~/.claude/`, it still resolves correctly because the path exists on disk. Verify hook script permissions after sync:

```bash
ls -l ~/.claude-2/hooks/SessionEnd/
chmod +x ~/.claude-2/hooks/SessionEnd/*
```

### `sessions-index.json` race condition

**Symptom:** An external tool (such as `cc-session-sync` or `qmd`) reports incomplete or stale session data. Sessions created recently are missing from search results.

**Cause:** Two tools updated `sessions-index.json` simultaneously. The later write overwrote the earlier write's changes.

**Claude Code resume is unaffected** — it scans `.jsonl` files directly and ignores the index file. Only external tools that rely on the index are affected.

**Fix:**

```bash
# Delete the index; tools will regenerate it on next run
rm ~/.claude/projects/sessions-index.json
```

To prevent recurrence, stagger tool schedules in crontab so concurrent writes are unlikely:

```bash
0 */6 * * * /path/to/tool-a
30 */6 * * * /path/to/tool-b
```

### Memory isolation causing missing context

**Symptom:** A session resumed in `~/.claude-2` lacks bookmarks, memory entries, or context variables that were set in `~/.claude`.

**Cause:** The `memory/` directory is intentionally isolated per account. There is no automatic sync for memory.

**Fix (opt-in sharing):** Symlink all accounts' memory to the primary:

```bash
for ACCT in ~/.claude ~/.claude-[0-9]*; do
    if [ -d "$ACCT/memory" ] && [ ! -L "$ACCT/memory" ]; then
        mv "$ACCT/memory" "$ACCT/memory.bak.$(date +%s)"
        ln -sf ~/.claude/memory "$ACCT/memory"
    fi
done
```

After this, any write to memory from any account lands in `~/.claude/memory`. If you prefer one-time copy without ongoing sharing, use `rsync` instead of symlink.

### PROJ_KEY collision

**Symptom:** Two unrelated projects show each other's sessions in the resume dialog.

**Cause:** Claude Code encodes the project path into a key (PROJ_KEY). Paths that differ by `-` versus `/` in different positions can produce the same encoding. Example: `/a/b-c` and `/a-b/c` may collide.

**Fix:** This is a limitation of Claude Code's session indexing, not of the multi-account setup. Rename one of the projects to use a clearly distinct path:

```bash
# Avoid ambiguous path structures
# Prefer:  ~/Projects/my-app
# Avoid:   ~/Projects-old/my/app
```

---

## Diagnostics

### Verify Claude Code version

```bash
claude --version
CLAUDE_CONFIG_DIR=~/.claude-2 claude --version
```

Both should return the same binary version. If they differ, the shell function from Ep1 may be resolving to different binaries.

### Check which config directory is active

```bash
echo $CLAUDE_CONFIG_DIR
```

If empty, Claude Code is using `~/.claude`. In the shell function from Ep1, the variable is set inline for the subprocess and does not persist in the shell environment.

### Validate `settings.json` syntax

```bash
python3 -m json.tool ~/.claude/settings.json > /dev/null && echo "OK"
python3 -m json.tool ~/.claude-2/settings.json > /dev/null && echo "OK"
python3 -m json.tool ~/.claude-3/settings.json > /dev/null && echo "OK"
```

A non-zero exit with no "OK" output means the file contains invalid JSON.

### Inspect symlink state

```bash
# plugins symlinks
file ~/.claude-2/plugins ~/.claude-3/plugins ~/.claude-4/plugins

# projects symlinks (from Ep2)
file ~/.claude/projects ~/.claude-2/projects ~/.claude-3/projects
```

Each should report `symbolic link to ~/.claude/plugins` or `~/.claude-shared/projects` respectively. A line reading `directory` or `broken symbolic link` needs attention.

### Check file ownership and permissions

```bash
ls -la ~/.claude/settings.json
ls -la ~/.claude-2/settings.json
ls -la ~/.claude/hooks/SessionEnd/
```

Hook scripts must be executable (`-rwxr-xr-x`). Settings files must be readable by the current user. On a single-user macOS machine these rarely cause problems, but they can appear after copying files from another machine or restoring from a backup.

### Locate session log files

Claude Code writes session logs to the shared projects directory. To verify where they are landing:

```bash
find ~/.claude-shared/projects -name "*.jsonl" | head -10
```

If sessions from a particular account are not appearing, verify that account's projects symlink resolves correctly:

```bash
ls ~/.claude-2/projects | head -5
```

### Check disk usage

If disk usage has grown unexpectedly after setting up session sharing:

```bash
du -sh ~/.claude-shared/projects/
du -sh ~/.claude/projects.bak.*
du -sh ~/.claude-2/projects.bak.*
```

Backups created by the Ep2 migration script accumulate over time. Remove backups older than 30 days when you are confident the setup is stable:

```bash
find ~/.claude ~/.claude-[0-9]* -maxdepth 1 -name "projects.bak.*" -mtime +30 -exec rm -rf {} \;
```

---

## Series close

Across three posts, the setup covers isolation (Ep1: CLAUDE_CONFIG_DIR separates each account into its own config root), continuity (Ep2: a symlinked projects directory gives every account visibility into the same session history), and consistency (this post: a bidirectional sync script keeps settings, hooks, commands, and plugins aligned across accounts while preserving account-specific overrides). The practical limit of this approach is that it runs on a single OS user's filesystem — cross-user sharing requires group permission configuration and falls outside the typical use case. For most developers running two to four Claude Code accounts on one machine, the combination of CLAUDE_CONFIG_DIR isolation, shared session storage, and sync-on-SessionEnd is a stable, low-maintenance operating pattern.
