sync
Scannednpx machina-cli add skill Borda/.home/sync --openclawSync 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
- PROJECT root:
$(git rev-parse --show-toplevel) - HOME_EXPANDED:
$(eval echo ~) - File manifest:
git ls-files .claude/andgit ls-files .codex/— git-tracked only; gitignored paths (state/, logs/, settings.local.json, auth.json) excluded automatically - settings.json transform: replace
node .claude/hooks/withnode $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
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.jsonand.codex/auth.jsonare 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 applyonce the report looks correct - Follow-up: after
/sync apply, run/auditto confirm the propagated config is structurally sound
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
- 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: run the provided dry-run blocks for .claude/ and settings.json to preview differences
- 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