ASK KNOX
beta
LESSON 188

Fleet Deployment — Syncing Your Setup Across Machines

Your Claude Code setup — skills, hooks, agents, settings — is only useful if it's on every machine you work on. Here's how to deploy additively across a fleet without breaking existing configs.

6 min read·Claude Code Operations

The configuration work from the previous lessons — hooks, skills, agent definitions, tuned settings — lives in ~/.claude/ on your primary development machine. It took real time to build and represents genuine operational knowledge. The day you SSH into a server to debug a production issue and Claude Code runs with factory defaults, that investment is invisible.

Fleet deployment solves this. Your configuration should follow you to every machine where you run Claude Code.

The Additive Principle

The cardinal rule of fleet deployment: deploy additively. Never overwrite.

Remote machines may have their own configurations. A server may have hooks specific to its environment. A teammate's machine may have agent definitions they developed locally. Overwriting their ~/.claude/ with your primary config destroys their setup.

Additive deployment means:

  • Copy skills to the remote — only add files, never delete existing ones
  • Copy hooks to the remote — only add files, never delete existing ones
  • Merge settings.json — add new keys you want to propagate, preserve keys that already exist
  • Copy agent definitions — only add, never delete

from fleet deployment rsync commands. No exceptions.

What to Deploy

The ~/.claude/ directory has the following relevant subdirectories:

~/.claude/
  settings.json          # Global settings — merge, do not overwrite
  skills/                # Skill SKILL.md files — additive
  hooks/                 # Hook scripts — additive, need chmod +x
  agents/                # Agent definition .md files — additive

Each category has different deployment requirements.

Skills are markdown files with no path dependencies. They transfer cleanly to any machine. No modifications needed after transfer.

Hooks are bash scripts that execute on the receiving machine. They need to be executable (chmod +x). They may contain absolute paths — specifically, the settings.json on the receiving machine must reference hooks using paths valid on that machine, not paths from your primary.

Agent definitions are markdown files with no path dependencies. They transfer cleanly.

settings.json is the most sensitive. It contains path references, environment variables, and hook registrations. Overwriting it entirely breaks the receiving machine's hook registration. Merge new keys; preserve existing ones.

macOS and Linux Deployment via rsync

For Unix machines, rsync is the right tool. The deployment commands:

# Deploy skills — additive, no delete
rsync -avz ~/.claude/skills/ user@host:~/.claude/skills/

# Deploy hooks — additive, no delete, ensure executable
rsync -avz --chmod=+x ~/.claude/hooks/ user@host:~/.claude/hooks/

# Deploy agent definitions — additive, no delete
rsync -avz ~/.claude/agents/ user@host:~/.claude/agents/

The flags:

  • -a — archive mode (preserves permissions, timestamps, symlinks)
  • -v — verbose output so you can see what transferred
  • -z — compress during transfer
  • --chmod=+x — ensure hook scripts are executable on the destination
  • No --delete — additive only

Note the trailing slash on both source and destination path. rsync -avz ~/.claude/skills/ with a trailing slash copies the contents of the directory, not the directory itself. Without it, you get ~/.claude/skills/skills/ on the destination — a common mistake.

Merging settings.json

Settings cannot be rsynced directly because rsync does not know how to merge JSON. The merge happens on the remote via jq.

The pattern is to read the current remote settings, add or update the specific keys you want to propagate, and write back. This preserves all existing keys on the remote and only adds what you specify:

ssh user@host 'cat ~/.claude/settings.json | \
  jq ".cleanupPeriodDays = 365 | \
      .env.CLAUDE_BASH_MAX_OUTPUT = \"100000\"" \
  > /tmp/merged_settings.json && \
  mv /tmp/merged_settings.json ~/.claude/settings.json'

This command reads the existing remote settings, adds cleanupPeriodDays and CLAUDE_BASH_MAX_OUTPUT, writes to a temp file, then atomically replaces settings.json. If cleanupPeriodDays already exists on the remote with a different value, jq overwrites it with your specified value. All other existing keys are preserved untouched.

The Hook Path Problem

Hooks registered in settings.json reference absolute paths:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Read",
        "hooks": [
          {
            "type": "command",
            "command": "/Users/alice/.claude/hooks/read-guard.sh"
          }
        ]
      }
    ]
  }
}

When this settings.json is deployed to a remote machine where the user is deploy and the home directory is /home/deploy, the path /Users/alice/.claude/hooks/read-guard.sh does not exist. The hook registration is invalid.

Solutions:

Option 1 — Per-machine settings.json. Do not sync hook registrations in settings.json. Each machine maintains its own registrations using paths local to that machine. You sync only the hook scripts themselves via rsync, and a one-time setup on each machine registers them with the correct local paths.

Option 2 — Relative path hooks. Use $HOME in your hook commands rather than absolute paths. This requires the hook commands to be written as shell expansions, which settings.json does not always support natively. Check your Claude Code version's settings schema before relying on this.

Option 3 — Deployment script with sed substitution. After rsyncing hooks, run a sed pass on the remote settings.json to replace the primary machine's username with the remote machine's username:

REMOTE_USER=$(ssh user@host 'whoami')
ssh user@host "sed -i 's|/Users/alice/|/home/${REMOTE_USER}/|g' ~/.claude/settings.json"

Option 1 is the most reliable for heterogeneous fleets. Option 3 works well for homogeneous fleets with predictable home directory structures.

Windows Machines

rsync requires a Unix environment. For Windows machines, use scp instead:

# Create directories first
ssh user@windowshost 'mkdir -Force C:\Users\user\.claude\skills'
ssh user@windowshost 'mkdir -Force C:\Users\user\.claude\agents'

# Copy skills and agents
scp -r ~/.claude/skills/ user@windowshost:C:/Users/user/.claude/skills/
scp -r ~/.claude/agents/ user@windowshost:C:/Users/user/.claude/agents/

Critical limitation: bash hooks will not execute on Windows. A hook script that is valid on macOS or Linux requires a Unix shell. Windows does not have one by default. Skills and agent definitions are markdown files — they are OS-agnostic. Hooks are executables — they need PowerShell equivalents on Windows.

If you have Windows machines in your fleet, maintain two hook implementations for each hook: the bash version for Unix machines and a .ps1 PowerShell version for Windows. The settings.json on each Windows machine references the .ps1 version.

Verification After Deployment

After deploying to any remote machine, verify that the deployment succeeded:

# Check skill count
ssh user@host 'ls ~/.claude/skills/ | wc -l'

# Check hook executability
ssh user@host 'ls -la ~/.claude/hooks/'

# Verify settings.json is valid JSON
ssh user@host 'python3 -c "import json; json.load(open(\"${HOME}/.claude/settings.json\"))" && echo "Valid JSON"'

# Check key settings propagated
ssh user@host 'cat ~/.claude/settings.json | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get(\"cleanupPeriodDays\", \"MISSING\"))"'

A deployment that appears to succeed but produces a malformed settings.json is worse than no deployment — it silently disables all configured hooks. Always validate.

Wrapping It in a Skill

Manual deployment commands are error-prone. Wrap the entire deployment in a skill that you invoke when you add a new machine or after significant configuration updates:

---
name: fleet-deploy
description: Deploy Claude Code configuration to a remote machine
allowed-tools: Bash
---

# Fleet Deploy Skill

Deploy skills, hooks, agents, and merged settings to a target machine.

## Required Input
- TARGET_USER: remote username
- TARGET_HOST: hostname or IP address

## Steps

1. Verify ~/.claude/ subdirectories exist on remote
2. rsync skills (additive, no delete)
3. rsync hooks (additive, chmod +x)
4. rsync agents (additive, no delete)
5. Merge settings.json keys via jq on remote
6. Run verification checks
7. Report files deployed and settings confirmed

Lesson 188 Drill

List every machine where you run Claude Code. For each one, SSH in and check:

  1. Does ~/.claude/settings.json exist?
  2. How many skills are in ~/.claude/skills/?
  3. Are hooks present and executable?
  4. What is the cleanupPeriodDays value?

The gap between your primary machine and each remote is the deployment debt. Write the rsync commands to close it. Run them. Verify.

Then wrap those commands in a fleet-deploy skill so the next time you set up a new machine, it takes five minutes instead of fifty.