Get the FREE Ultimate OpenClaw Setup Guide →

sync

Scanned
npx machina-cli add skill Borda/.home/sync --openclaw
Files (1)
SKILL.md
7.0 KB
<objective>

Sync git-tracked files from project .claude/ and .codex/ to their home equivalents (~/.claude/ and ~/.codex/). Uses rsync driven by git ls-files as the file manifest — only git-tracked files are ever synced; runtime state, secrets, and temp files are excluded automatically.

Project always wins: files in home that have no counterpart in the project are left untouched. No deletion, ever.

</objective> <inputs>
  • $ARGUMENTS: optional
    • Omitted → dry-run: show what would change, no files written
    • apply → apply sync: rsync files and report outcome
</inputs> <constants>
  • PROJECT root: $(git rev-parse --show-toplevel)
  • HOME_EXPANDED: $(eval echo ~)
  • File manifest: git ls-files .claude/ and git ls-files .codex/ — git-tracked only; gitignored paths (state/, logs/, settings.local.json, auth.json) excluded automatically
  • settings.json transform: replace node .claude/hooks/ with node $HOME_EXPANDED/.claude/hooks/ (absolute). Covers all hook entries — statusLine, task-log, and future additions.
  • Never synced: settings.local.json, .codex/auth.json — not git-tracked, excluded automatically
  • No context: fork — sync reads files from both project and home then writes decisions; forking would give the skill an isolated context with no access to the home directory state it needs to compare against
</constants> <workflow>

Step 1: Resolve paths

PROJECT="$(git rev-parse --show-toplevel)"
HOME_EXPANDED="$(eval echo ~)"
HOME_CLAUDE="$HOME_EXPANDED/.claude"
HOME_CODEX="$HOME_EXPANDED/.codex"

Step 2: Dry-run — show what would change

Each bash block must be self-contained (redeclare variables at the top). Do not rely on variables from a previous tool call.

.claude/ files (excluding settings.json):

PROJECT="$(git rev-parse --show-toplevel)"
HOME_EXPANDED="$(eval echo ~)"
HOME_CLAUDE="$HOME_EXPANDED/.claude"
git -C "$PROJECT" ls-files .claude/ \
  | grep -vE 'settings\.json$|settings\.local\.json$' \
  | sed 's|^\.claude/||' \
  | rsync -av --dry-run --files-from=- "$PROJECT/.claude/" "$HOME_CLAUDE/"

rsync prints only files that would change; identical files are silently skipped.

settings.json — transform then compare (self-contained):

PROJECT="$(git rev-parse --show-toplevel)"
HOME_EXPANDED="$(eval echo ~)"
HOME_CLAUDE="$HOME_EXPANDED/.claude"
SETTINGS_TMP="$(mktemp /tmp/settings_sync_XXXXXX.json)"
sed "s|node .claude/hooks/|node $HOME_EXPANDED/.claude/hooks/|g" \
  "$PROJECT/.claude/settings.json" > "$SETTINGS_TMP"
CHANGED=$(rsync --checksum --itemize-changes --dry-run \
  "$SETTINGS_TMP" "$HOME_CLAUDE/settings.json" 2>&1)
if [ -z "$CHANGED" ]; then
  echo "✓ IDENTICAL settings.json"
else
  echo "DIFFERS — semantic summary:"
  # Permissions added (in project, not in home)
  comm -23 \
    <(jq -r '.permissions.allow[]' "$SETTINGS_TMP" | sort) \
    <(jq -r '.permissions.allow[]' "$HOME_CLAUDE/settings.json" | sort) \
    | sed 's/^/  + /'
  # Permissions removed (in home, not in project)
  comm -13 \
    <(jq -r '.permissions.allow[]' "$SETTINGS_TMP" | sort) \
    <(jq -r '.permissions.allow[]' "$HOME_CLAUDE/settings.json" | sort) \
    | sed 's/^/  - /'
  # Hook matcher changes
  diff \
    <(jq -r '.hooks // {} | to_entries[] | "\(.key): \(.value[].matcher // "")"' "$SETTINGS_TMP" 2>/dev/null | sort) \
    <(jq -r '.hooks // {} | to_entries[] | "\(.key): \(.value[].matcher // "")"' "$HOME_CLAUDE/settings.json" 2>/dev/null | sort) \
    | grep '^[<>]' | sed 's/^< /  project: /' | sed 's/^> /  home:    /'
fi
rm -f "$SETTINGS_TMP"

.codex/ files:

PROJECT="$(git rev-parse --show-toplevel)"
HOME_EXPANDED="$(eval echo ~)"
HOME_CODEX="$HOME_EXPANDED/.codex"
git -C "$PROJECT" ls-files .codex/ \
  | sed 's|^\.codex/||' \
  | rsync -av --dry-run --files-from=- "$PROJECT/.codex/" "$HOME_CODEX/"

If $ARGUMENTS is empty: print the combined dry-run output and offer /sync apply. Stop here.

Step 3: Apply (only when $ARGUMENTS == "apply")

Each bash block must be self-contained (redeclare variables at the top).

.claude/ files (excluding settings.json):

PROJECT="$(git rev-parse --show-toplevel)"
HOME_EXPANDED="$(eval echo ~)"
HOME_CLAUDE="$HOME_EXPANDED/.claude"
git -C "$PROJECT" ls-files .claude/ \
  | grep -vE 'settings\.json$|settings\.local\.json$' \
  | sed 's|^\.claude/||' \
  | rsync -av --files-from=- "$PROJECT/.claude/" "$HOME_CLAUDE/"

settings.json — transform and apply (self-contained, content-based comparison):

PROJECT="$(git rev-parse --show-toplevel)"
HOME_EXPANDED="$(eval echo ~)"
HOME_CLAUDE="$HOME_EXPANDED/.claude"
SETTINGS_TMP="$(mktemp /tmp/settings_sync_XXXXXX.json)"
sed "s|node .claude/hooks/|node $HOME_EXPANDED/.claude/hooks/|g" \
  "$PROJECT/.claude/settings.json" > "$SETTINGS_TMP"
CHANGED=$(rsync --checksum --itemize-changes \
  "$SETTINGS_TMP" "$HOME_CLAUDE/settings.json" 2>&1)
rm -f "$SETTINGS_TMP"
if [ -n "$CHANGED" ]; then
  echo "merged   settings.json"
else
  echo "✓ unchanged settings.json"
fi

rsync --checksum compares file content (not mtime), so it transfers only when content actually differs.

.codex/ files:

PROJECT="$(git rev-parse --show-toplevel)"
HOME_EXPANDED="$(eval echo ~)"
HOME_CODEX="$HOME_EXPANDED/.codex"
git -C "$PROJECT" ls-files .codex/ \
  | sed 's|^\.codex/||' \
  | rsync -av --files-from=- "$PROJECT/.codex/" "$HOME_CODEX/"

Step 4: Verify and report outcome (apply mode only)

PROJECT="$(git rev-parse --show-toplevel)"
HOME_EXPANDED="$(eval echo ~)"
# JSON validity
jq empty "$HOME_EXPANDED/.claude/settings.json" && echo "settings.json: valid"

# Counts
echo ".claude files: $(git -C "$PROJECT" ls-files .claude/ | wc -l | tr -d ' ')"
echo ".codex files:  $(git -C "$PROJECT" ls-files .codex/  | wc -l | tr -d ' ')"
## Sync Outcome — <date>

| Target          | Result             |
|-----------------|--------------------|
| .claude/ files  | N transferred      |
| settings.json   | merged / unchanged |
| .codex/ files   | N transferred      |
| JSON validity   | valid              |
</workflow> <notes>
  • File manifest from git ls-files — adding a new agent, skill, hook, or codex agent to git automatically includes it in future syncs; no skill edits needed
  • settings.local.json and .codex/auth.json are never synced — gitignored, excluded automatically
  • All node .claude/hooks/ paths in settings.json are rewritten to absolute — relative paths fail when hooks fire outside this project directory
  • Project owns what it tracks; home-only files are never touched or deleted
  • rsync skips identical files — only changed files are transferred, making repeated runs cheap
  • Run /sync (dry-run) first, then /sync apply once the report looks correct
  • Follow-up: after /sync apply, run /audit to confirm the propagated config is structurally sound
</notes>

Source

git clone https://github.com/Borda/.home/blob/main/.claude/skills/sync/SKILL.mdView on GitHub

Overview

Drift-detect and sync git-tracked .claude/ and .codex/ config from your project to the home directory. It uses rsync driven by a git ls-files manifest to show what would change in dry-run mode, and 'apply' performs the actual sync. Runtime state, secrets, and temp files are excluded automatically, and home-only files are left untouched.

How This Skill Works

The tool builds a manifest from git ls-files for .claude/ and .codex/, then rsyncs the changes to ~/.claude and ~/.codex. By default it runs a dry-run to preview changes; when you pass 'apply', it executes the rsync to perform the sync. A settings.json transformation rewrites node .claude/hooks/ to the home path, ensuring hook references point to ~/.claude/hooks/; only git-tracked files are synced and non-tracked home files or secrets are never touched.

When to Use It

  • Setting up a new workstation and want to mirror project config to home
  • Testing updates to settings.json or hook paths before applying
  • Collaborating across machines and needing consistent .claude/ and .codex/ versions
  • Previewing changes without writing any files (dry-run)
  • Preserving home-only files while syncing project-tracked config

Quick Start

  1. Step 1: Resolve paths: PROJECT=$(git rev-parse --show-toplevel); HOME_EXPANDED=$(eval echo ~); HOME_CLAUDE="$HOME_EXPANDED/.claude"; HOME_CODEX="$HOME_EXPANDED/.codex"
  2. Step 2: Dry-run — show what would change: run the provided dry-run blocks for .claude/ and settings.json to preview differences
  3. Step 3: Apply to sync: run the command with the argument apply to perform rsync and report the outcome

Best Practices

  • Ensure only the desired directories are git-tracked (.claude/ and .codex/) before syncing
  • Always run a dry-run first to preview changes
  • Back up your home directory before applying changes
  • Review the settings.json transformation to confirm hooks point to the home path
  • Keep secrets and sensitive data out of git-tracked files; rely on automatic exclusions

Example Use Cases

  • On a new workstation, run a dry-run to preview changes to ~/.claude and ~/.codex, then run with apply to sync
  • Add a new file under .claude/hooks/ in the project and sync to propagate the update to home
  • Update the settings.json in the project to point to the home hooks path, compare changes, then apply
  • Synchronize across teammates by committing the changes and pulling on another machine
  • Verify that files present only in home are preserved and not deleted during sync

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers