11 min read
AI assisted

Claude Code Settings Sync and Troubleshooting

Sync matrix, migration steps, common failures, and diagnostic commands for running multiple Claude Code accounts on one machine

Series — Multi-Account Claude Code Operations
  1. 1. Multi-Account Claude Code via CLAUDE_CONFIG_DIR
  2. 2. Sharing Claude Code Sessions via Symlinked .jsonl
  3. 3. Claude Code Settings Sync and Troubleshooting

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:

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:

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

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

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

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:

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

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:

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

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:

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:

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

Diagnostics

Verify Claude Code version

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

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

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

# 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

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:

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:

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

Check disk usage

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

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:

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.