Workflow: Dotfiles Sync

SkillAI & models

Use 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.

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

TermMeaningPath
localThe actual dotfiles on this machine (chezmoi target)$HOME/.<file>
remoteThe 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
  • dotfiles helper 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)

  1. Run dotfiles status and dotfiles sync in parallel.

  2. Run dotfiles inspect chezmoi-diff to get full diff content for every entry.

  3. 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.json existed only in remote while local opencode.json was 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_codex caused Codex sqlite/native-host/runtime files to recur every sync; the correct action was to convert to non-exact dot_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-deps triggered 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.
  4. 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 .chezmoiignore if 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)

  1. Re-add local-drift files to remote: chezmoi re-add <paths>
  2. Remove intentional-removal files from remote: chezmoi forget --force <paths>
  3. Review git -C ~/.local/share/chezmoi status --short
  4. Commit with descriptive message
  5. Fetch, then merge remote changes (NEVER rebase)
  6. Push
  7. Only after remote is settled: chezmoi apply --force for remote-only and script-rename paths
  8. Re-run dotfiles sync to verify clean state
  9. Report final state

Team

RoleResponsibility
dotfiles operatorScans, auto-classifies ALL diffs, builds enriched report
git reconcilerCommits, merges, pushes the chezmoi remote repo
escalation agentOnly 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 sync completes clean after execution
  • Any committed remote changes are pushed successfully
  • chezmoi apply --force runs only after remote is reconciled
  • True conflicts (both sides changed structurally) are escalated, never auto-merged
  • Final dotfiles status reflects either clean state or explicitly accepted volatile noise
  • Volatile paths not in .chezmoiignore are 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