Project 34: Configuration Sync - Cross-Machine Settings
Project 34: Configuration Sync - Cross-Machine Settings
Comprehensive Learning Guide Synchronize Claude Code settings, hooks, skills, and preferences across multiple development machines
Table of Contents
- Learning Objectives
- Deep Theoretical Foundation
- Complete Project Specification
- Real World Outcome
- Solution Architecture
- Phased Implementation Guide
- Testing Strategy
- Common Pitfalls & Debugging
- Extensions & Challenges
- Resources
- Self-Assessment Checklist
Learning Objectives
By completing this project, you will master:
-
Configuration Hierarchy Understanding: Learn how Claude Code’s settings cascade from enterprise to user to project levels, and how to manage each layer effectively.
-
Dotfile Management Patterns: Apply proven patterns from tools like chezmoi, GNU Stow, and yadm to manage Claude Code configuration files across machines.
-
Sync Algorithms: Implement conflict detection, resolution strategies, and change propagation for distributed configuration management.
-
Secure Secret Management: Handle API keys, tokens, and sensitive settings using encryption (git-crypt, age) without exposing them in version control.
-
Change Detection: Build systems that detect configuration drift between machines using hashing and comparison algorithms.
-
Git-Based Synchronization: Use Git as a sync backend with proper branch strategies, merge handling, and atomic updates.
Deep Theoretical Foundation
Claude Code Configuration Hierarchy
Claude Code uses a layered configuration system where settings cascade from general to specific:
Configuration Precedence (highest to lowest):
=============================================
1. ENTERPRISE CONFIG (Highest Priority)
Location: Set by organization (MDM, group policy)
Scope: Organization-wide policies
Override: Cannot be overridden by user
Example: Approved MCP servers, security policies
2. PROJECT CONFIG
Location: ./.claude/settings.json, ./CLAUDE.md
Scope: Single project/repository
Override: Can override user settings (except enterprise)
Example: Project-specific hooks, coding conventions
3. USER CONFIG
Location: ~/.claude/settings.json, ~/.claude/CLAUDE.md
Scope: All projects on this machine
Override: Base settings, overridable by project
Example: Personal preferences, default output style
4. DEFAULT CONFIG (Lowest Priority)
Location: Built into Claude Code
Scope: Fallback values
Override: Always overridable
Example: Default model, token limits
+-------------------+
| Enterprise Config | <-- Cannot override
+--------+----------+
|
v
+-------------------+
| Project Config | <-- ./CLAUDE.md
+--------+----------+
|
v
+-------------------+
| User Config | <-- ~/.claude/
+--------+----------+
|
v
+-------------------+
| Default Config | <-- Built-in
+-------------------+
What Lives Where:
~/.claude/ # USER CONFIGURATION
+-- CLAUDE.md # Global instructions (SYNC)
+-- settings.json # User preferences (SYNC - encrypted)
+-- hooks/ # User-defined hooks (SYNC)
| +-- pre-tool-validator.ts
| +-- post-session-logger.ts
+-- skills/ # User-defined skills (SYNC)
| +-- my-commit-skill.md
| +-- code-review.md
+-- styles/ # Output styles (SYNC)
| +-- concise.md
| +-- detailed.md
+-- cache/ # Temporary cache (IGNORE)
| +-- session-cache.json
+-- sessions/ # Session history (IGNORE)
| +-- session-abc123.json
+-- mcp/ # MCP server configs (SYNC - partial)
+-- servers.json
./.claude/ # PROJECT CONFIGURATION
+-- settings.json # Project overrides
+-- CLAUDE.md # Project instructions (usually in git)
Reference: “Pragmatic Programmer” by Hunt & Thomas, Chapter 3 discusses dotfiles and configuration management principles.
Sync Strategies and Tradeoffs
Different sync strategies have different tradeoffs:
Strategy Comparison:
===================
PUSH/PULL (Git-based)
=====================
Pros:
- Version history
- Branching for experiments
- Works offline
- Mature tooling
Cons:
- Manual sync required
- Merge conflicts possible
- Learning curve for git
Workflow:
Machine A: git add -A && git commit && git push
Machine B: git pull && apply
-----------------------------------------------------
BIDIRECTIONAL SYNC (Dropbox/Syncthing-style)
=============================================
Pros:
- Automatic
- Real-time updates
- No manual intervention
Cons:
- Conflict resolution complex
- Requires always-on service
- Network dependency
Workflow:
Changes automatically propagate when connected
-----------------------------------------------------
LAST-WRITE-WINS
===============
Pros:
- Simple to implement
- No conflicts
- Predictable
Cons:
- Data loss risk
- No merge capability
- Timestamp dependency
Workflow:
Most recent change overwrites all copies
-----------------------------------------------------
RECOMMENDED APPROACH: Push/Pull + Selective Bidirectional
=========================================================
- Use git for version history and intentional sync
- Use timestamps for conflict detection
- Manual resolution with diff preview
- Selective sync (some files always auto-merge)
Reference: “Designing Data-Intensive Applications” by Martin Kleppmann, Chapter 5 covers replication, sync, and conflict resolution in distributed systems.
Secret Management in Sync Systems
Never sync secrets in plaintext. Use encryption:
Secret Management Options:
=========================
1. GIT-CRYPT
=========
- Transparent encryption for git repos
- GPG-based key management
- Automatic encrypt/decrypt on push/pull
Setup:
$ git-crypt init
$ git-crypt add-gpg-user YOUR_GPG_ID
.gitattributes:
settings.json filter=git-crypt diff=git-crypt
*.secret filter=git-crypt diff=git-crypt
Result: Files encrypted in repo, decrypted locally
2. AGE ENCRYPTION
===============
- Modern, simple encryption
- No GPG complexity
- Key file or passphrase
Encrypt:
$ age -r age1... settings.json > settings.json.age
Decrypt:
$ age -d -i key.txt settings.json.age > settings.json
3. ENVIRONMENT VARIABLES
======================
- Secrets in .env (not synced)
- References in config (synced)
settings.json (synced):
{ "apiKeyRef": "$OPENAI_API_KEY" }
.env (not synced, per-machine):
OPENAI_API_KEY=sk-...
4. SECRET MANAGER INTEGRATION
==========================
- 1Password CLI
- HashiCorp Vault
- AWS Secrets Manager
Reference in config:
{ "apiKey": "op://vault/item/field" }
Reference: “Practical Security” by Roman Zabicki, Chapter 7 covers secret management patterns.
Change Detection and Drift
Detect when configurations diverge:
Change Detection Flow:
=====================
Machine A Machine B
+-----------+ +-----------+
| Config | | Config |
+-----------+ +-----------+
| |
v v
+-----------+ +-----------+
| Hash | | Hash |
| SHA-256 | | SHA-256 |
+-----------+ +-----------+
| |
+------------+---------------+
|
v
+----------------+
| Compare |
| Hashes |
+----------------+
|
+--------------+--------------+
| |
v v
+-----------+ +-----------+
| Match | | Different |
| (sync) | | (drift) |
+-----------+ +-----------+
|
v
+----------------+
| Conflict |
| Resolution |
| - Show diff |
| - User choice |
| - Auto-merge |
+----------------+
Drift Detection Manifest:
========================
{
"lastSync": "2024-12-22T10:30:00Z",
"machine": "laptop-a",
"files": {
"CLAUDE.md": {
"hash": "sha256:a1b2c3...",
"modified": "2024-12-22T09:15:00Z"
},
"settings.json": {
"hash": "sha256:d4e5f6...",
"modified": "2024-12-22T08:00:00Z"
},
"hooks/pre-tool.ts": {
"hash": "sha256:g7h8i9...",
"modified": "2024-12-21T16:45:00Z"
}
}
}
Complete Project Specification
Functional Requirements
Core Features (Must Have):
| Feature | Description | Priority |
|---|---|---|
| Push configuration | Upload local config to remote | P0 |
| Pull configuration | Download remote config to local | P0 |
| Conflict detection | Identify files changed on both sides | P0 |
| Diff preview | Show changes before applying | P0 |
| Secret encryption | Encrypt sensitive files | P0 |
| Ignore patterns | Exclude cache, sessions, etc. | P0 |
| Status command | Show sync state and drift | P1 |
| History command | Show recent sync operations | P1 |
| Rollback command | Restore previous configuration | P2 |
| Multi-machine tracking | Track sync state per machine | P2 |
CLI Commands:
# Push local configuration to remote
claude-sync push [--force] [--message "description"]
# Pull remote configuration to local
claude-sync pull [--force] [--dry-run]
# Show sync status
claude-sync status
# Show differences between local and remote
claude-sync diff
# Initialize sync repository
claude-sync init [--backend git|s3|custom]
# Configure sync settings
claude-sync config [key] [value]
# Show sync history
claude-sync log [--limit 10]
# Rollback to previous state
claude-sync rollback [commit-or-timestamp]
Real World Outcome
You’ll have a sync tool:
Example Usage:
# On Machine A - save current config
$ claude-sync push
Syncing Claude Code configuration...
Scanning ~/.claude/ for changes...
Changes to push:
+---------------------------------------+----------+--------+
| File | Size | Status |
+---------------------------------------+----------+--------+
| ~/.claude/CLAUDE.md | 2.3KB | modified |
| ~/.claude/settings.json | 1.1KB | encrypted |
| ~/.claude/hooks/pre-tool-validate.ts | 856B | new |
| ~/.claude/hooks/post-session-log.ts | 1.2KB | unchanged |
| ~/.claude/skills/commit-helper.md | 2.1KB | modified |
| ~/.claude/styles/concise.md | 512B | unchanged |
+---------------------------------------+----------+--------+
Pushing to remote...
Commit: abc1234 "Sync from machine-a at 2024-12-22 14:30"
Configuration synced to remote
# On Machine B - pull config
$ claude-sync pull
Fetching Claude Code configuration...
Changes detected:
+---------------------------------------+----------+--------+
| File | Size | Action |
+---------------------------------------+----------+--------+
| ~/.claude/CLAUDE.md | 2.3KB | update |
| ~/.claude/hooks/pre-tool-validate.ts | 856B | create |
| ~/.claude/skills/commit-helper.md | 2.1KB | update |
+---------------------------------------+----------+--------+
Apply changes? [y/n/diff]: diff
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -10,6 +10,10 @@
## Preferences
+
+### New Section Added
+- Always use TypeScript
+- Prefer functional patterns
Apply changes? [y/n]: y
Applying changes...
Decrypting settings.json...
Configuration applied
Your Claude Code is now in sync with machine-a
Conflict Resolution:
$ claude-sync pull
Fetching Claude Code configuration...
CONFLICT detected in ~/.claude/CLAUDE.md
Both local and remote have changes since last sync.
Local changes (machine-b, 2024-12-22 15:00):
+ Added Python coding guidelines
Remote changes (machine-a, 2024-12-22 14:30):
+ Added TypeScript preferences
How do you want to resolve?
[l] Keep local version
[r] Use remote version
[m] Merge both changes (opens editor)
[d] Show full diff
Choice: m
Opening merge editor...
[After merge completes]
Merged configuration saved.
Push this merged version? [y/n]: y
Pushed merged configuration to remote
Solution Architecture
System Architecture Diagram
+------------------------------------------------------------------+
| CLAUDE-SYNC SYSTEM |
+------------------------------------------------------------------+
| |
| +------------------+ +------------------+ +-----------+ |
| | CLI Interface | | Sync Engine | | Storage | |
| |------------------| |------------------| |-----------| |
| | - push/pull |---->| - Change detect |---->| - Git repo| |
| | - status/diff | | - Conflict res | | - S3 (opt)| |
| | - config |<----| - Encryption |<----| - Custom | |
| +------------------+ +------------------+ +-----------+ |
| | | | |
| v v v |
| +------------------+ +------------------+ +-----------+ |
| | File Scanner | | Crypto Layer | | Transport | |
| |------------------| |------------------| |-----------| |
| | - Hash files | | - age encrypt | | - git ops | |
| | - Ignore rules | | - decrypt | | - http | |
| | - Manifest | | - key mgmt | | - file cp | |
| +------------------+ +------------------+ +-----------+ |
| |
+------------------------------------------------------------------+
|
v
+-------------------+
| ~/.claude/ |
| Configuration |
+-------------------+
Data Flow Diagram
PUSH FLOW:
=========
~/.claude/ Sync Engine Remote Storage
=========== =========== ==============
Files
|
v
+----------+
| Scanner |-------> Manifest
+----------+ (hashes)
| |
v v
+----------+ +-----------+
| Filter | | Compare |-----> Changed files list
| (ignore) | | with last |
+----------+ +-----------+
| |
v v
+----------+ +-----------+
| Encrypt | | Resolve |-----> Conflict resolution
| secrets | | conflicts |
+----------+ +-----------+
| |
+--------+----------+
|
v
+-----------+
| Commit & |-----> git commit && git push
| Push | OR S3 upload
+-----------+
|
v
+-----------+
| Update |-----> Local manifest
| manifest |
+-----------+
PULL FLOW:
=========
Remote Storage Sync Engine ~/.claude/
============== =========== ===========
Remote
manifest
|
v
+----------+
| Fetch |-------> Remote files list
| metadata |
+----------+
|
v
+----------+ +-----------+
| Compare |<----| Local |
| manifests| | manifest |
+----------+ +-----------+
|
v
+----------+
| Download |-------> Changed files
| changes |
+----------+
|
v
+----------+ +-----------+
| Decrypt |---->| Apply |-----> Updated ~/.claude/
| secrets | | changes |
+----------+ +-----------+
|
v
+----------+
| Update |-------> Local manifest
| manifest |
+----------+
Module Breakdown
src/
+-- index.ts # CLI entry point
+-- cli/
| +-- commands/
| | +-- push.ts # Push command handler
| | +-- pull.ts # Pull command handler
| | +-- status.ts # Status command handler
| | +-- diff.ts # Diff command handler
| | +-- config.ts # Config command handler
| | +-- init.ts # Init command handler
| | +-- rollback.ts # Rollback command handler
| +-- prompts.ts # Interactive prompts
| +-- output.ts # Formatted output
|
+-- core/
| +-- scanner.ts # File scanning and hashing
| +-- manifest.ts # Manifest management
| +-- diff.ts # Diff generation
| +-- conflict.ts # Conflict detection/resolution
| +-- merge.ts # File merging logic
|
+-- storage/
| +-- backend.ts # Storage backend interface
| +-- git-backend.ts # Git-based storage
| +-- s3-backend.ts # S3-based storage (optional)
| +-- local-backend.ts # Local folder (testing)
|
+-- crypto/
| +-- encryption.ts # Encryption/decryption
| +-- keys.ts # Key management
| +-- age.ts # age encryption wrapper
|
+-- config/
| +-- settings.ts # Sync settings
| +-- ignore.ts # Ignore patterns
| +-- paths.ts # Path constants
|
+-- types/
+-- index.ts # TypeScript types
+-- manifest.ts # Manifest types
+-- config.ts # Config types
Phased Implementation Guide
Phase 1: Scanner and Manifest (Day 1)
Goal: Scan ~/.claude/ and generate file manifest with hashes.
Milestone: Running scanner outputs JSON manifest of all sync-able files.
Tasks:
- Project Setup
mkdir claude-sync && cd claude-sync npm init -y npm install commander chalk ora glob crypto npm install -D typescript @types/node vitest npx tsc --init - Create Scanner (
src/core/scanner.ts)import { createHash } from 'crypto'; import { readFileSync, statSync } from 'fs'; import { glob } from 'glob'; export interface FileEntry { path: string; hash: string; size: number; modified: string; } export async function scanDirectory(dir: string): Promise<FileEntry[]> { const ignorePatterns = ['cache/**', 'sessions/**', '*.log']; const files = await glob('**/*', { cwd: dir, ignore: ignorePatterns, nodir: true }); return files.map(file => { const fullPath = `${dir}/${file}`; const content = readFileSync(fullPath); const stats = statSync(fullPath); return { path: file, hash: createHash('sha256').update(content).digest('hex'), size: stats.size, modified: stats.mtime.toISOString() }; }); } - Create Manifest Manager (
src/core/manifest.ts)- Load/save manifest JSON
- Compare two manifests
- Identify added/modified/deleted files
- Test Scanner
npx tsx src/core/scanner.ts
Success Criteria: Scanner correctly identifies files and generates hashes.
Phase 2: Git Backend (Day 2)
Goal: Push and pull files to/from a git repository.
Milestone: Files sync between test directories via git.
Tasks:
- Create Git Backend (
src/storage/git-backend.ts)import { execSync } from 'child_process'; export class GitBackend { constructor(private repoPath: string) {} async init(): Promise<void> { execSync('git init', { cwd: this.repoPath }); } async push(files: FileEntry[], message: string): Promise<string> { // Stage files for (const file of files) { execSync(`git add "${file.path}"`, { cwd: this.repoPath }); } // Commit execSync(`git commit -m "${message}"`, { cwd: this.repoPath }); // Push execSync('git push origin main', { cwd: this.repoPath }); // Return commit hash return execSync('git rev-parse HEAD', { cwd: this.repoPath }) .toString() .trim(); } async pull(): Promise<void> { execSync('git pull origin main', { cwd: this.repoPath }); } } - Create Storage Interface (
src/storage/backend.ts)- Define common interface for all backends
- Allow swapping git for S3 later
- Implement Push Command (
src/cli/commands/push.ts)- Scan files
- Generate manifest
- Copy to git repo
- Commit and push
- Implement Pull Command (
src/cli/commands/pull.ts)- Pull from git
- Compare manifests
- Copy changed files to ~/.claude/
Success Criteria: Push/pull works between two directories.
Phase 3: Encryption (Day 3)
Goal: Encrypt sensitive files before syncing.
Milestone: settings.json is encrypted in git but readable locally.
Tasks:
- Create Crypto Module (
src/crypto/encryption.ts)import { execSync } from 'child_process'; export function encryptFile(inputPath: string, outputPath: string, publicKey: string): void { execSync(`age -r ${publicKey} -o "${outputPath}" "${inputPath}"`); } export function decryptFile(inputPath: string, outputPath: string, keyPath: string): void { execSync(`age -d -i "${keyPath}" -o "${outputPath}" "${inputPath}"`); } - Define Encryption Rules
// .claude-sync/config.json { "encrypt": [ "settings.json", "*.secret", "mcp/servers.json" ], "publicKey": "age1...", "keyFile": "~/.claude-sync/key.txt" } - Integrate with Push/Pull
- On push: encrypt matching files before commit
- On pull: decrypt after download
- Key Setup Command (
src/cli/commands/init.ts)- Generate age key pair
- Store private key locally (never sync)
- Store public key in config (sync)
Success Criteria: Encrypted files in git, decrypted locally.
Phase 4: Conflict Resolution (Day 4)
Goal: Detect and resolve conflicts when both sides change.
Milestone: User can choose how to resolve conflicts interactively.
Tasks:
- Create Conflict Detector (
src/core/conflict.ts)export interface Conflict { path: string; localHash: string; remoteHash: string; baseHash: string; // Last synced version localModified: string; remoteModified: string; } export function detectConflicts( local: Manifest, remote: Manifest, base: Manifest ): Conflict[] { const conflicts: Conflict[] = []; for (const [path, localEntry] of Object.entries(local.files)) { const remoteEntry = remote.files[path]; const baseEntry = base.files[path]; if (remoteEntry && baseEntry) { // Both changed since base - conflict! if (localEntry.hash !== baseEntry.hash && remoteEntry.hash !== baseEntry.hash && localEntry.hash !== remoteEntry.hash) { conflicts.push({ path, localHash: localEntry.hash, remoteHash: remoteEntry.hash, baseHash: baseEntry.hash, localModified: localEntry.modified, remoteModified: remoteEntry.modified }); } } } return conflicts; } - Create Resolution Prompts (
src/cli/prompts.ts)- Show conflict details
- Options: keep local, use remote, merge, show diff
- Open merge editor if requested
- Implement Diff Display (
src/core/diff.ts)- Use diff library for side-by-side comparison
- Color-coded output
- Update Pull Command
- Check for conflicts before applying
- Prompt for resolution
- Apply resolutions
Success Criteria: Conflicts detected and resolved interactively.
Phase 5: CLI Polish (Day 5)
Goal: Complete CLI with status, history, and rollback.
Milestone: Production-ready sync tool.
Tasks:
- Status Command (
src/cli/commands/status.ts)- Show local changes since last sync
- Show remote changes (requires fetch)
- Show sync state (in sync, ahead, behind, diverged)
- History Command (
src/cli/commands/log.ts)- Show recent sync operations
- Include machine name, timestamp, files changed
- Rollback Command (
src/cli/commands/rollback.ts)- List available restore points
- Restore specific version
- Confirm before applying
- Polish
- Progress spinners during operations
- Colored, formatted output
- Helpful error messages
- –help with examples
- Create bin Script
{ "bin": { "claude-sync": "./dist/index.js" } }
Success Criteria: All commands work smoothly with good UX.
Testing Strategy
Unit Tests: Scanner
// tests/core/scanner.test.ts
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdirSync, writeFileSync, rmSync } from 'fs';
import { scanDirectory } from '../../src/core/scanner';
describe('scanner', () => {
const testDir = '/tmp/claude-sync-test';
beforeEach(() => {
mkdirSync(testDir, { recursive: true });
});
afterEach(() => {
rmSync(testDir, { recursive: true });
});
it('scans files and generates hashes', async () => {
writeFileSync(`${testDir}/file1.txt`, 'content1');
writeFileSync(`${testDir}/file2.txt`, 'content2');
const entries = await scanDirectory(testDir);
expect(entries).toHaveLength(2);
expect(entries[0].hash).toBeDefined();
expect(entries[0].hash).toHaveLength(64); // SHA-256 hex
});
it('ignores specified patterns', async () => {
mkdirSync(`${testDir}/cache`, { recursive: true });
writeFileSync(`${testDir}/file.txt`, 'content');
writeFileSync(`${testDir}/cache/temp.txt`, 'temp');
const entries = await scanDirectory(testDir);
expect(entries).toHaveLength(1);
expect(entries[0].path).toBe('file.txt');
});
it('detects file modifications via hash change', async () => {
writeFileSync(`${testDir}/file.txt`, 'original');
const before = await scanDirectory(testDir);
writeFileSync(`${testDir}/file.txt`, 'modified');
const after = await scanDirectory(testDir);
expect(before[0].hash).not.toBe(after[0].hash);
});
});
Integration Tests: Sync Flow
// tests/integration/sync.test.ts
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { SyncEngine } from '../../src/core/sync-engine';
describe('sync flow', () => {
let engine: SyncEngine;
let localDir: string;
let remoteDir: string;
beforeEach(() => {
localDir = '/tmp/claude-sync-local';
remoteDir = '/tmp/claude-sync-remote';
engine = new SyncEngine(localDir, remoteDir);
});
it('pushes new files to remote', async () => {
writeFileSync(`${localDir}/new-file.txt`, 'content');
const result = await engine.push();
expect(result.pushed).toContain('new-file.txt');
expect(existsSync(`${remoteDir}/new-file.txt`)).toBe(true);
});
it('pulls new files from remote', async () => {
writeFileSync(`${remoteDir}/remote-file.txt`, 'remote content');
const result = await engine.pull();
expect(result.pulled).toContain('remote-file.txt');
expect(existsSync(`${localDir}/remote-file.txt`)).toBe(true);
});
it('detects conflicts', async () => {
// Setup base state
writeFileSync(`${localDir}/shared.txt`, 'base');
await engine.push();
await engine.pull();
// Modify both sides
writeFileSync(`${localDir}/shared.txt`, 'local change');
writeFileSync(`${remoteDir}/shared.txt`, 'remote change');
const result = await engine.pull({ dryRun: true });
expect(result.conflicts).toContain('shared.txt');
});
});
Common Pitfalls & Debugging
Pitfall 1: Ignoring Machine-Specific Files
Symptom: Sync breaks on different machines due to absolute paths.
Bad:
// settings.json contains absolute paths
{
"pythonPath": "/Users/alice/anaconda/bin/python"
}
Good:
// Use environment variables or relative paths
{
"pythonPath": "${PYTHON_PATH:-python}"
}
Fix: Add machine-specific patterns to ignore list or use path variables.
Pitfall 2: Encrypting Key File
Symptom: Can’t decrypt after sync because key was encrypted.
Bad:
// .claude-sync/config.json
{
"encrypt": ["*.json"] // Encrypts the key reference!
}
Good:
// Be explicit about what to encrypt
{
"encrypt": ["settings.json", "mcp/servers.json"],
"neverEncrypt": ["config.json", "manifest.json"]
}
Pitfall 3: Git Conflicts in Binary Files
Symptom: Git can’t merge binary or encrypted files.
Bad:
git pull # Fails with merge conflict in encrypted file
Good:
# Configure git to use "ours" or "theirs" for encrypted files
# .gitattributes
*.age merge=binary
Or handle at application level by always preferring one side and prompting user.
Pitfall 4: Lost Changes on Force Push
Symptom: Remote changes lost after force push.
Bad:
claude-sync push --force # Overwrites remote without checking
Good:
# Always fetch and check first
claude-sync push
# Shows: "Remote has changes not in local. Pull first or use --force"
Pitfall 5: Timestamp Skew
Symptom: Sync decisions wrong due to clock differences between machines.
Debug:
# Check system time
date
# Check file timestamps
stat ~/.claude/CLAUDE.md
Fix: Use git commit timestamps (server-side) rather than file modification times for ordering decisions.
The Interview Questions They’ll Ask
Prepare to answer these:
- “How would you handle a sync conflict where both machines modified the same hook?”
- Detect via three-way comparison (local, remote, base)
- Show diff to user
- Options: keep local, use remote, merge manually
- For code files, attempt automatic merge with git merge-file
- “What’s your strategy for storing secrets that need to sync?”
- Never store plaintext secrets in sync repo
- Use age encryption with public/private key pair
- Private key stays local, never synced
- Public key in config for adding new machines
- Environment variables for truly machine-specific secrets
- “How do you detect configuration drift?”
- Hash all synced files on each machine
- Store manifest with last-sync state
- Compare current hashes to manifest
- Detect: local changes, remote changes, conflicts
- Optional: periodic background check
- “What happens if sync fails partway through?”
- Atomic operations where possible
- Transaction log of pending changes
- Rollback capability to last good state
- Lock file to prevent concurrent syncs
- Clear error messages about what failed
- “How would you roll back a bad configuration sync?”
- Git history provides all versions
- List available restore points (commits)
- Preview what would change
- Apply restore with confirmation
- Update manifest to prevent immediate re-sync
Hints in Layers
Hint 1: Use Git The simplest approach is a private git repo for your dotfiles. ~/.claude/ becomes managed by git or symlinked via stow.
Hint 2: Hash for Changes Hash file contents to detect changes without transferring entire files. SHA-256 is fast and collision-resistant.
Hint 3: Encryption Layer
Use age or git-crypt to encrypt sensitive files before committing. Both integrate well with git workflows.
Hint 4: Ignore Patterns Some files should never sync: cached data, machine-specific paths, temporary files, session history.
Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| Dotfile management | “Pragmatic Programmer” by Hunt & Thomas | Ch. 3: Basic Tools |
| Sync algorithms | “Designing Data-Intensive Applications” by Kleppmann | Ch. 5: Replication |
| Secret management | “Practical Security” by Roman Zabicki | Ch. 7: Secrets |
| Git internals | “Pro Git” by Scott Chacon | Ch. 10: Git Internals |
| Change detection | “Algorithms” by Sedgewick | Ch. 5: Hashing |
Extensions & Challenges
Extension 1: Real-Time Sync
Use file system watchers to sync immediately on change:
import { watch } from 'fs';
watch(claudeDir, { recursive: true }, (event, filename) => {
if (!isIgnored(filename)) {
debounce(() => syncFile(filename), 1000);
}
});
Extension 2: Multi-Backend Support
Add S3, Dropbox, or custom server backends:
interface StorageBackend {
push(files: FileEntry[]): Promise<void>;
pull(): Promise<FileEntry[]>;
getManifest(): Promise<Manifest>;
}
class S3Backend implements StorageBackend { ... }
class DropboxBackend implements StorageBackend { ... }
Extension 3: Team Sync
Extend to sync shared team configurations:
claude-sync team add teammate@email.com
claude-sync team share hooks/code-review.ts
claude-sync team pull # Get team shared configs
Self-Assessment Checklist
Conceptual Understanding
- Can you explain the Claude Code configuration hierarchy?
- Can you describe push/pull vs bidirectional sync tradeoffs?
- Can you explain how to detect conflicts in distributed systems?
- Can you list what files should and shouldn’t be synced?
- Can you explain encryption options for secrets?
Implementation Skills
- Can you scan a directory and generate file hashes?
- Can you implement git-based push and pull?
- Can you encrypt/decrypt files with age?
- Can you detect and present conflicts?
- Can you create a user-friendly CLI?
Code Quality
- Is your code organized into logical modules?
- Are storage backends swappable?
- Is error handling comprehensive?
- Are operations atomic where needed?
- Can another developer add a new backend?
The Core Question You’ve Answered
“How do you maintain consistent Claude Code behavior across all your development machines?”
When you have a laptop, desktop, and work machine, keeping hooks, skills, and preferences in sync manually is error-prone. This project automates that synchronization.
By building this sync tool, you have mastered:
- Configuration Management: Understanding the hierarchy and what to sync
- Distributed Sync: Detecting changes, conflicts, and resolving them
- Secret Handling: Keeping sensitive data secure in transit
- CLI Development: Building tools developers want to use
You can now maintain a consistent Claude Code experience everywhere you work.
Project Guide Version 1.0 - December 2025