Workflow: Dotfiles Sync
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
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)
Run dotfiles status and dotfiles sync in parallel.
Run dotfiles inspect chezmoi-diff to 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.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.
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)
- 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 --force for remote-only and script-rename paths
- Re-run
dotfiles sync to 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 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
1---2name: dotfiles-sync3description: Use when reconciling chezmoi local-vs-remote drift, reviewing pending machine-state changes, or performing safe remote-first dotfiles sync across machines.4---56# Workflow: Dotfiles Sync78NEVER REBASE. ALWAYS MERGE.910## Terminology1112| Term | Meaning | Path |13|------|---------|------|14| **local** | The actual dotfiles on this machine (chezmoi target) | `$HOME/.<file>` |15| **remote** | The chezmoi source repo, git-tracked and pushed | `$HOME/.local/share/chezmoi` |1617Always use "local" and "remote" when presenting drift reports. Never use chezmoi's internal "source/target" terminology in user-facing output.1819## Trigger2021Run this workflow when you need to:22- reconcile local-vs-remote dotfile drift23- inspect pending chezmoi changes before apply24- perform a safe remote-first sync across machines2526## Inputs2728- Chezmoi remote repo at `$HOME/.local/share/chezmoi`29- Local state under `$HOME`30- `dotfiles` helper available on PATH31- Optional handoff context in `.lev/pm/handoffs/`32- Workstream state at `.lev/pm/workstreams/dotfiles-sync/state/workstream.yaml`3334## Steps3536### Phase 1: Scan + Auto-Classify (immediate, no user interaction)37381. Run `dotfiles status` and `dotfiles sync` in parallel.392. Run `dotfiles inspect chezmoi-diff` to get full diff content for every entry.403. **Auto-classify every diff** into exactly one of these buckets. There is NO "manual review" bucket — every diff MUST be classified:41 - **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.42 - **local-drift** — local changed intentionally, remote is stale (MM where target newer)43 - **conflict** — both changed in the same area (MM where both recent, content diverges structurally)44 - **volatile** — runtime noise: `.tmp`, `installation_id`, cache files, lockfiles, IDE state (auto NO-GO, silent skip)45 - **script-rename** — chezmoi script naming convention change (R status). Inspect source to confirm equivalence, then classify as GO.46 - **intentional-removal** — file deleted locally on purpose (check memory + handoff history for prior decisions before asking user)4748 Classification rules:49 - If the path is in `.chezmoiignore`, skip entirely — it should not appear in the report.50 - If the path matches known volatile patterns (`.tmp`, `installation_id`, `plugins/`, `cache/`, `*.log`, `*.lock`), classify as **volatile** automatically.51 - If the diff shows only whitespace, line-ending, or timestamp changes, classify as **volatile**.52 - 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.53 - If MM and target is newer, classify as **local-drift**.54 - If R (rename), read both old and new source files to determine if content is equivalent or changed.55 - 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.56 - 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.57 - Check `.lev/pm/handoffs/` and memory for prior decisions about specific paths before classifying ambiguous items.58594. For each classified diff, determine GO/NO-GO:60 - **remote-only** → GO only when the remote file is clearly desired on this machine; otherwise ESCALATE or intentional-removal/forget61 - **local-drift** → GO (re-add from local to remote)62 - **conflict** → ESCALATE (present both sides, require explicit user decision)63 - **volatile** → NO-GO (skip silently, recommend adding to `.chezmoiignore` if not already there)64 - **script-rename** → GO only after side-effect review confirms the script is safe for this sync scope65 - **intentional-removal** → GO (remove from remote via `chezmoi forget`)6667### Phase 2: Enriched Report (first response to user)6869Present a single enriched report. Use `/now` or a clear table format. The report MUST include:7071```72DOTFILES SYNC REPORT — {hostname} — {date}7374SUMMARY75 Total diffs: N76 Auto-resolved: N (GO)77 Escalations: N (need your call)78 Volatile skipped: N7980ACTIONS (GO)81| # | Path | Direction | What changes | Classification |82|---|------|-----------|-------------|----------------|83| 1 | .claude/settings.json | local → remote | +3 hooks, -1 deprecated key | local-drift |84| 2 | .codex/AGENTS.md | local → remote | updated agent list | local-drift |85| ... | | | | |8687ESCALATIONS (need decision)88| # | Path | Local intent | Remote intent | My recommendation |89|---|------|-----------|-------------|-------------------|90| (none if clean) | | | | |9192VOLATILE (auto-skipped)93| Path | Reason |94|------|--------|95| .codex/.tmp | runtime temp dir |9697CHEZMOIIGNORE CANDIDATES98| Path | Why |99|------|-----|100| (paths that keep showing up as volatile but aren't in .chezmoiignore yet) |101```102103For 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.104105End the report with a single go/no-go prompt:106- If zero escalations: "All N actions are auto-classified GO. Execute? [y/n]"107- If escalations exist: "N actions ready, M need your call. Resolve escalations first, then execute."108109### Phase 3: Execute (only after user confirms)1101115. Re-add local-drift files to remote: `chezmoi re-add <paths>`1126. Remove intentional-removal files from remote: `chezmoi forget --force <paths>`1137. Review `git -C ~/.local/share/chezmoi status --short`1148. Commit with descriptive message1159. Fetch, then merge remote changes (NEVER rebase)11610. Push11711. Only after remote is settled: `chezmoi apply --force` for remote-only and script-rename paths11812. Re-run `dotfiles sync` to verify clean state11913. Report final state120121## Team122123| Role | Responsibility |124|------|---------------|125| dotfiles operator | Scans, auto-classifies ALL diffs, builds enriched report |126| git reconciler | Commits, merges, pushes the chezmoi remote repo |127| escalation agent | Only activates when true conflicts exist (both sides changed structurally) |128129## Outputs130131- Enriched drift report (first response, always)132- Updated chezmoi remote repo state133- Updated local home state after apply134- Dry-run report at `~/.local/state/lev/dotfiles-sync-plan.latest.txt`135- Workstream state update at `.lev/pm/workstreams/dotfiles-sync/state/workstream.yaml`136137## Validation138139- Every diff is classified — zero items in any "manual review" or "unclassified" bucket140- `dotfiles sync` completes clean after execution141- Any committed remote changes are pushed successfully142- `chezmoi apply --force` runs only after remote is reconciled143- True conflicts (both sides changed structurally) are escalated, never auto-merged144- Final `dotfiles status` reflects either clean state or explicitly accepted volatile noise145- Volatile paths not in `.chezmoiignore` are flagged as candidates for addition