@sebastienrousseau/dotfiles
Version:
The Trusted Shell Platform — Universal dotfiles managed by Chezmoi. Features Bash & Zsh for macOS, Linux & WSL. Rust modern tooling & enterprise-grade security.
108 lines (80 loc) • 4.62 kB
Markdown
---
render_with_liquid: false
---
# Drift Detection & Remediation
This page documents how this repo detects drift between the chezmoi
source-of-truth and what's actually deployed on a host, how to read
the report, and how to remediate. Managed under
[#875](https://github.com/sebastienrousseau/dotfiles/issues/875).
## What "drift" means here
Four distinct classes are tracked. The same `dot drift` command (and
the nightly CI workflow) surfaces all four.
| Class | Meaning | How to detect | Typical fix |
|---|---|---|---|
| **Managed drift** | A chezmoi-managed file's deployed copy differs from what a fresh `chezmoi apply` would produce. The standard case. | `chezmoi status` (M / MM / A / R rows) | Either update the source so apply is idempotent, or accept the deployed change and re-add. |
| **Untracked source** | The chezmoi source tree contains files git doesn't know about — usually in-progress local edits that haven't been committed. | `git -C <source-dir> ls-files --others --exclude-standard` | Commit, stash, or `.gitignore` the file. |
| **Orphan deployed** | A file under `$HOME` was previously chezmoi-managed but the source has since been deleted. Chezmoi no longer claims it, so a fresh apply leaves it behind silently. | Inventoried in `${XDG_STATE_HOME}/dotfiles/orphans` (populated by `dot heal` / `dot drift`) | `chezmoi remove --force` the path, or re-add the source if the file is still wanted. |
| **Stale source** | The deployed file is *newer* than its source. The next `chezmoi apply` would silently revert the user's hand-edit. Reverse-drift trap. | Compare mtimes for each managed target vs the resolved source-path | Promote the deployed change into the source (`chezmoi re-add`) or revert the deployed file. |
## Reading the report
```bash
dot drift # human-readable (uses the ui.sh formatting)
dot drift --json # single JSON object — used by the nightly workflow
dot drift --diff # also print `chezmoi diff` for managed drift
```
JSON shape:
```json
{
"managed_drift": 0,
"untracked_source": 0,
"orphan_deployed": 0,
"stale_source": 0,
"total": 0
}
```
Exit code: `0` if every class is clean; `1` if any drift is found;
`2` if a prerequisite (chezmoi, git) is missing.
## How the nightly check works
`.github/workflows/drift-detection.yml` runs `dot drift --json` against
a fresh checkout of `master` every day at 04:00 UTC. If `total != 0`
it opens (or updates) a tracking issue labelled
`type:chore + priority:medium` with the JSON summary, full
`chezmoi diff`, and `chezmoi status` attached as a workflow artifact.
The workflow itself ignores the failing exit code (`|| true`) for the
dashboard step — the actionable signal is the issue, not a red CI
indicator.
## Force a local drift check
```bash
dot drift # default — what you'd run before opening a PR
dot drift --json | jq '.' # for scripting / dashboards
```
To force a full re-comparison after a tool upgrade or a force-apply:
```bash
chezmoi apply --refresh-externals # refetch external sources
dot drift # re-scan
```
## Historical incidents
### 2026-05-12 — `core.hooksPath` drift
The deployed `~/.gitconfig` contained a `hooksPath = ~/.git-templates/hooks`
line that wasn't in `dot_gitconfig.tmpl`. The line had been added
directly to the deployed file (manually, not via chezmoi), then sat
silently for weeks while the global `commit-msg` hook (at
`~/.config/git/hooks/commit-msg`) never fired — because `hooksPath`
was pointing at an empty directory. The result: every commit
authored on this machine silently shipped without the
`Assisted-by:` trailer mandated by `dot_claude/CLAUDE.md`.
Detection failure: no nightly drift check existed at the time.
Resolution: commit `f060683b` brought `hooksPath` into the chezmoi
template; this drift class is exactly what the new `stale_source`
signal catches going forward.
This incident is the canonical worked example for why the four-class
report exists rather than just `chezmoi status`.
## Configuration surface
| Variable | Default | Purpose |
|---|---|---|
| `DOTFILES_DRIFT_SHOW_DIFF` | `0` | When `1`, append `chezmoi diff` (excluding scripts/install/tests) to the report. Equivalent to `--diff`. |
## References
- `scripts/diagnostics/drift-dashboard.sh` — the dashboard itself.
- `.github/workflows/drift-detection.yml` — the nightly scanner.
- `tests/unit/diagnostics/test_drift_dashboard.sh` — JSON contract test.
- `dot heal` / `dot rollback` — drift remediation commands.
- Issue [#875](https://github.com/sebastienrousseau/dotfiles/issues/875).