| name | chezmoi-management |
| description | Meta-skill for mizchi's chezmoi dotfiles. Invoke ONLY when the user explicitly asks to manage / diff / apply chezmoi sources, add a skill to dotfiles, audit the APM vs chezmoi boundary, or initialize a new machine. Covers source location, diff/apply flow, skill addition, pre-push (pkfire + secretlint). Do NOT auto-invoke when the task only happens to touch a path under ~/.claude/, ~/.config/, or ~/.zshrc — consult only on explicit dotfile-management intent. |
chezmoi Management (mizchi personal)
Personal dotfiles operations notes. The official chezmoi docs are already sufficient, so this document focuses solely on how things concretely work in my own environment.
Baseline environment
| Item | Value |
|---|
| Source directory | ~/.local/share/chezmoi/ |
| Remote | https://github.com/mizchi/chezmoi-dotfiles.git |
| Branch | main |
Packages / programs.* | nix home-manager via dot_config/home-manager/flake.nix (= home-manager standalone or nix-darwin integrated mode) |
| pre-push | pkfire (Taskfile.pkl + pkf hooks install) + secretlint |
| Post-apply hook | run_after_apm-install.sh → apm install --global --target claude |
Responsibility split with nix home-manager
This repo runs alongside home-manager — neither is a superset of the other:
- home-manager owns: CLI packages (
home.packages), programs.* wrappers (git / direnv / zsh / etc), and tool installs that flow through Nix derivations (e.g. pkfire, pkl, actionlint, awscli2, pkgs.go). Edit dot_config/home-manager/common.nix then darwin-rebuild switch --flake .#macos (or home-manager switch).
- chezmoi owns: dotfile content that
programs.* can't shape — ~/.claude/, ~/.codex/, ~/.apm/, ad-hoc ~/.config/<editor-or-shell>/, ~/.zshrc, kept-templated paths.
- APM owns: public Claude Code skills under
~/.claude/skills/<name>/. .chezmoiignore lists each APM-managed skill so chezmoi never re-deploys over apm install -g.
Rule: if a programs.* wrapper exists in home-manager for a tool, configure it there — do not also stage the dotfile via chezmoi. Mixing creates two source-of-truth conflicts that surface as "I edited the file but my change keeps reverting".
Layout cheat sheet
~/.local/share/chezmoi/
├── dot_apm/ → ~/.apm/ (APM config)
├── dot_claude/ → ~/.claude/ (Claude Code)
│ ├── CLAUDE.md.tmpl
│ ├── settings.json.tmpl
│ ├── rules/
│ └── skills/ → ~/.claude/skills/ (self-authored skills)
├── dot_codex/ → ~/.codex/
├── dot_config/ → ~/.config/ (helix, mise, sheldon, starship, zellij, zsh, home-manager)
│ └── home-manager/ → ~/.config/home-manager/ (flake.nix / common.nix / darwin.nix — Nix-evaluated; chezmoi only stages the files, Nix does the actual install)
├── dot_zshrc → ~/.zshrc
├── run_once_before_install-brew.sh (bootstrap: clone Homebrew prefix to ~/brew before nix-darwin first activation)
└── run_after_apm-install.sh (every apply: apm install --global --target claude)
Meaning of filename prefixes
| prefix | Meaning | Example |
|---|
dot_ | Leading . | dot_zshrc → .zshrc |
executable_ | +x permission | executable_setup.sh → setup.sh (755) |
private_ | 0600 permission | private_key → key (600) |
.tmpl | Go template | CLAUDE.md.tmpl → CLAUDE.md expanded with hostname etc. |
run_once_ | Run only the first time | run_once_install_brew.sh |
run_after_ | Run every time after apply | run_after_apm-install.sh |
Daily flow
(1) Check what differs
chezmoi diff
chezmoi status
Direction sense for chezmoi diff: the output is in - dest / + target order (following git diff's old=current / new=after-apply convention).
- lines = content in dest (the current real file) — will be removed by apply
+ lines = content in target (the expected state after source.tmpl expansion) — will be added by apply
chezmoi apply moves in the direction of aligning dest to target (source → dest)
When in doubt, the fastest way to confirm is to look at both cat <source> and cat <dest>.
The columns for chezmoi status are [source][dest]:
M = modified, A = added, D = deleted, ? = untracked (not in source)
MM → changes on both sides (need to absorb the manually-edited dest into source)
M → source unchanged but dest was modified (stray change)
DA → marked as deleted in source but exists in dest (junk like node_modules/)
(2) Edited dest (the real file) → absorb into source
chezmoi add ~/.zshrc
chezmoi re-add
chezmoi re-add ~/.claude/CLAUDE.md
(3) Edited source (repo side) → apply to dest
chezmoi diff
chezmoi apply
chezmoi apply ~/.claude/CLAUDE.md
chezmoi apply --verbose
(4) Open source in an editor
chezmoi edit ~/.zshrc
chezmoi edit -a ~/.zshrc
chezmoi cd
New machine initialization
Install order is nix → chezmoi → apm. brew prefix is bootstrapped by chezmoi's run_once_before_install-brew.sh so nix-darwin's homebrew module can reference ~/brew on first activation.
curl -fsSL https://install.determinate.systems/nix | sh -s -- install
chezmoi init https://github.com/mizchi/chezmoi-dotfiles.git --apply
nix run nix-darwin -- switch --flake ~/.config/home-manager#macos
cd $(chezmoi source-path)
pkf hooks install
Skill-addition flow (my personal routine)
- Develop and verify in
~/.claude/skills/<name>/ (for nix-setup, get nix build passing)
chezmoi add ~/.claude/skills/<name> to reflect into source
- Scripts with
+x get the executable_ prefix automatically
cd ~/.local/share/chezmoi && git status to confirm added files
git add dot_claude/skills/<name>/ + commit + git push origin main
If you edited an existing skill
Both chezmoi add and chezmoi re-add overwrite. In practice, re-add tends to produce fewer extraneous diffs:
chezmoi re-add ~/.claude/skills/nix-setup/SKILL.md
APM vs chezmoi boundary
~/.claude/skills/ is a mix of two systems:
| Kind | How it gets in | Managed at |
|---|
| APM-managed (external repo) | Fetched by apm install --global after apply | ~/.local/share/chezmoi/dot_apm/apm.yml |
| chezmoi-managed (self-authored) | Copied into source by chezmoi add | ~/.local/share/chezmoi/dot_claude/skills/<name>/ |
Currently installed via APM (excerpt from dot_apm/apm.yml):
moonbitlang/moonbit-agent-guide/* (moonbit-agent-guide, moonbit-refactoring, moonbit-c-binding)
mizchi/moonbit-practice/skills/moonbit-practice
mizchi/flaker/skills/flaker-setup
ast-grep/agent-skill/ast-grep
mizchi/tui.mbt/skills/tuimbt-practice
Decision criteria:
- Public / likely to be used from other repos → place in the upstream repo and register with APM
- Operational notes used only in my own environment / experimental skills → chezmoi-managed
If a directory of the same name exists in both locations, APM may overwrite it at install time, so watch out for name collisions.
Check for APM name collisions before adding a new skill to chezmoi:
grep "<skill-name>" ~/.local/share/chezmoi/dot_apm/apm.yml
chezmoi cd && grep -r "<skill-name>" dot_apm/
Editing tmpl files
CLAUDE.md.tmpl and settings.json.tmpl are Go templates:
chezmoi edit ~/.claude/CLAUDE.md
chezmoi execute-template < foo.tmpl
chezmoi data
Examples of template variables: {{ .chezmoi.os }}, {{ .chezmoi.hostname }}
Caution: Running chezmoi re-add against a .tmpl-managed file (CLAUDE.md.tmpl, settings.json.tmpl, etc.) will overwrite the .tmpl syntax in source with the expanded dest content. To update a .tmpl file, use chezmoi edit (which automatically opens the .tmpl side) or edit ~/.local/share/chezmoi/dot_claude/CLAUDE.md.tmpl directly.
Pre-check before re-add: always check whether the target file is .tmpl-managed with chezmoi source-path:
chezmoi source-path ~/.claude/CLAUDE.md
When you want to change the default value of a tmpl variable
Rather than rewriting the tmpl itself to a hard-coded value, put the variable in the [data] section of ~/.config/chezmoi/chezmoi.toml:
[data]
claude_default_mode = "auto"
github_username = "mizchi"
It's more flexible to preserve tmpl structures like {{ .claude_default_mode | default "acceptEdits" }} as-is and switch only the values per-host via [data] (leaves room to vary settings across machines).
pre-push (pkfire + secretlint)
secretlint runs on git push via pkfire (Taskfile.pkl → pkf hooks install → .git/hooks/pre-push), scoped to the diff range about to be pushed. Diffs containing API keys or tokens are rejected.
Re-arm the hook on a fresh checkout with pkf hooks install. Emergency bypass: git push --no-verify.
Common false positives:
- Example sha256 / hex strings (can trip when length resembles aws keys / github tokens)
- Sample values written in
.envrc comments
Remediation:
Exclusion syntax in .secretlintrc.json (excerpt):
{
"rules": [
{
"id": "@secretlint/secretlint-rule-preset-recommend",
"options": {
"allows": [
"/sha256-[a-f0-9]{64}/",
"fake-token-for-example",
"skill-examples/*"
]
}
}
]
}
allows takes either a regex (surrounded by /.../) or a literal string match. Per-file exclusion is possible via disabledRules + includes/excludes.
Don't use --no-verify (defeats the purpose).
Troubleshooting
chezmoi status shows a flood of DA (node_modules/ etc.)
Append to .chezmoiignore:
node_modules
**/.DS_Store
source and dest conflict
chezmoi merge ~/.zshrc
chezmoi forget ~/.something
chezmoi destroy ~/.something
The chezmoi merge backend is configured in the [merge] section of ~/.config/chezmoi/chezmoi.toml:
[merge]
command = "nvim"
args = ["-d", "{{ .Destination }}", "{{ .Source }}", "{{ .Target }}"]
If unset, chezmoi consults git config merge.tool. If both are unset, it falls back to vimdiff.
Apply broke things → roll back to a previous revision
chezmoi itself has no undo. Roll back via git on the source side:
chezmoi cd
git log --oneline -5
git reset --hard <rev>
cd -
chezmoi apply
Which files are managed
chezmoi managed
chezmoi unmanaged ~/
chezmoi managed ~/.claude
apply is slow
apm install inside run_after_apm-install.sh runs every time. If no skill updates are needed, skip it via an env var at apply time:
SKIP_APM=1 chezmoi apply
If unsupported, ignore.
Reference command cheat sheet
chezmoi diff
chezmoi status
chezmoi apply [-v]
chezmoi add <path>
chezmoi re-add [<path>]
chezmoi edit [-a] <path>
chezmoi merge <path>
chezmoi forget <path>
chezmoi managed [<path>]
chezmoi unmanaged <path>
chezmoi cd
chezmoi source-path
chezmoi execute-template <
chezmoi data
chezmoi doctor