Workflow: Dotfiles Sync
SkillAI & modelsUse when reconciling chezmoi local-vs-remote drift, reviewing pending machine-state changes, or performing safe remote-first dotfiles sync across machines.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Workflow: Dotfiles Sync skill
What this skill tells your AI
The instructions your AI receives, as published by lev-os/agents in skills/dotfiles-sync/SKILL.md and read by ahel’s review.
NEVER REBASE. ALWAYS MERGE.
Terminology
| Term | Meaning | Path |
|---|---|---|
| local | The actual dotfiles on this machine (chezmoi target) | $HOME/.<file> |
| remote | The chezmoi source repo, git-tracked and pushed | $HOME/.local/share/chezmoi |
Always use "local" and "remote" when presenting drift reports. Never use chezmoi's internal "source/target" terminology in user-facing output.
Trigger
Run this workflow when you need to:
- reconcile local-vs-remote dotfile drift
- inspect pending chezmoi changes before apply
- perform a safe remote-first sync across machines
Inputs
- Chezmoi remote repo at
$HOME/.local/share/chezmoi - Local state under
$HOME dotfileshelper available on PATH- Optional handoff context in
.lev/pm/handoffs/ - Workstream state at
.lev/pm/workstreams/dotfiles-sync/state/workstream.yaml
Steps
Phase 1: Scan + Auto-Classify (immediate, no user interaction)
-
Run
dotfiles statusanddotfiles syncin parallel. -
Run
dotfiles inspect chezmoi-diffto get full diff content for every entry. -
Auto-classify every diff into exactly one of these buckets. There is NO "manual review" bucket — every diff MUST be classified:
- remote-only — exists in remote, not reflected locally. This does not automatically mean local intentionally removed it; it can also mean this machine never installed or does not use an optional file.
- local-drift — local changed intentionally, remote is stale (MM where target newer)
- conflict — both changed in the same area (MM where both recent, content diverges structurally)
- volatile — runtime noise:
.tmp,installation_id, cache files, lockfiles, IDE state (auto NO-GO, silent skip) - script-rename — chezmoi script naming convention change (R status). Inspect source to confirm equivalence, then classify as GO.
- intentional-removal — file deleted locally on purpose (check memory + handoff history for prior decisions before asking user)
Classification rules:
- If the path is in
.chezmoiignore, skip entirely — it should not appear in the report. - If the path matches known volatile patterns (
.tmp,installation_id,plugins/,cache/,*.log,*.lock), classify as volatile automatically. - If the diff shows only whitespace, line-ending, or timestamp changes, classify as volatile.
- If DA and source is newer, inspect intent before recommending apply. Local absence can mean "not installed on this machine", not "removed globally". Example:
.config/opencode/oh-my-opencode.jsonexisted only in remote while localopencode.jsonwas newer; the correct action was to forget the remote-only optional file, not apply it locally. - If MM and target is newer, classify as local-drift.
- If R (rename), read both old and new source files to determine if content is equivalent or changed.
- If any parent source path starts with
exact_, audit the parent ownership before classifying child diffs. Exact directories make local runtime files appear as delete/re-add drift. Example:exact_dot_codexcaused Codex sqlite/native-host/runtime files to recur every sync; the correct action was to convert to non-exactdot_codex/AGENTS.md, not classify every runtime file independently. - Treat
run_onchange_*scripts as executable side effects, not normal file updates. Inspect the script before GO. Example: the QMD installer was narrow;install-depstriggered broad Homebrew upgrades and was not safe as a QMD-scoped action. - Check
.lev/pm/handoffs/and memory for prior decisions about specific paths before classifying ambiguous items.
-
For each classified diff, determine GO/NO-GO:
- remote-only → GO only when the remote file is clearly desired on this machine; otherwise ESCALATE or intentional-removal/forget
- local-drift → GO (re-add from local to remote)
- conflict → ESCALATE (present both sides, require explicit user decision)
- volatile → NO-GO (skip silently, recommend adding to
.chezmoiignoreif not already there) - script-rename → GO only after side-effect review confirms the script is safe for this sync scope
- intentional-removal → GO (remove from remote via
chezmoi forget)
Phase 2: Enriched Report (first response to user)
Present a single enriched report. Use /now or a clear table format. The report MUST include:
DOTFILES SYNC REPORT — {hostname} — {date}
SUMMARY
Total diffs: N
Auto-resolved: N (GO)
Escalations: N (need your call)
Volatile skipped: N
ACTIONS (GO)
| # | Path | Direction | What changes | Classification |
|---|------|-----------|-------------|----------------|
| 1 | .claude/settings.json | local → remote | +3 hooks, -1 deprecated key | local-drift |
| 2 | .codex/AGENTS.md | local → remote | updated agent list | local-drift |
| ... | | | | |
ESCALATIONS (need decision)
| # | Path | Local intent | Remote intent | My recommendation |
|---|------|-----------|-------------|-------------------|
| (none if clean) | | | | |
VOLATILE (auto-skipped)
| Path | Reason |
|------|--------|
| .codex/.tmp | runtime temp dir |
CHEZMOIIGNORE CANDIDATES
| Path | Why |
|------|-----|
| (paths that keep showing up as volatile but aren't in .chezmoiignore yet) |
For escalations, explain intent, not just timestamps. Timestamp is evidence only. Example: local OpenCode config newer than remote means local may win, but a single remote package-name correction can still be merged intentionally.
End the report with a single go/no-go prompt:
- If zero escalations: "All N actions are auto-classified GO. Execute? [y/n]"
- If escalations exist: "N actions ready, M need your call. Resolve escalations first, then execute."
Phase 3: Execute (only after user confirms)
- Re-add local-drift files to remote:
chezmoi re-add <paths> - Remove intentional-removal files from remote:
chezmoi forget --force <paths> - Review
git -C ~/.local/share/chezmoi status --short - Commit with descriptive message
- Fetch, then merge remote changes (NEVER rebase)
- Push
- Only after remote is settled:
chezmoi apply --forcefor remote-only and script-rename paths - Re-run
dotfiles syncto verify clean state - Report final state
Team
| Role | Responsibility |
|---|---|
| dotfiles operator | Scans, auto-classifies ALL diffs, builds enriched report |
| git reconciler | Commits, merges, pushes the chezmoi remote repo |
| escalation agent | Only activates when true conflicts exist (both sides changed structurally) |
Outputs
- Enriched drift report (first response, always)
- Updated chezmoi remote repo state
- Updated local home state after apply
- Dry-run report at
~/.local/state/lev/dotfiles-sync-plan.latest.txt - Workstream state update at
.lev/pm/workstreams/dotfiles-sync/state/workstream.yaml
Validation
- Every diff is classified — zero items in any "manual review" or "unclassified" bucket
dotfiles synccompletes clean after execution- Any committed remote changes are pushed successfully
chezmoi apply --forceruns only after remote is reconciled- True conflicts (both sides changed structurally) are escalated, never auto-merged
- Final
dotfiles statusreflects either clean state or explicitly accepted volatile noise - Volatile paths not in
.chezmoiignoreare flagged as candidates for addition
Signals
- GitHub stars
- 22
- Forks
- 2
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
dotfiles-sync- Source
- github.com/lev-os/agents