chezmoi — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited chezmoi (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Wrap chezmoi — manage dotfiles across machines using source-state semantics, Go templates, and at-rest encryption.
run_once_ install script.chezmoi.toml.tmpl config template for a fleet of machineschezmoi maps a source directory (~/.local/share/chezmoi, a git repo) to a target directory (the user's home). Source filenames carry attribute prefixes that determine target name and behavior. Source files with a .tmpl suffix are rendered as Go text/template and written to their target paths. Files under .chezmoitemplates/ are reusable template fragments included by other templates — they do not produce target files on their own. A config file at ~/.config/chezmoi/chezmoi.toml (typically generated from .chezmoi.toml.tmpl on first init) holds per-machine variables.
The two reasons people get into trouble:
~/.local/share/chezmoi/ instead of usingchezmoi edit $TARGET. This breaks templates and decrypt round-trips.
leak, forks leak, history is forever. Use a secret backend.
Two files, easy to confuse. Both can be in toml, yaml, json, or jsonc (auto-detected by extension; two formats present at once is an error, not "first one wins"):
| File | Path | Who writes it |
|---|---|---|
chezmoi.<fmt> (the config) | $XDG_CONFIG_HOME/chezmoi/chezmoi.<fmt>, default ~/.config/chezmoi/chezmoi.<fmt> (.toml most common). NOT in the source repo. | Generated by chezmoi init (or apply --init) from the template below. Per-machine. Holds secrets, API tokens, machine-specific settings — never commit. |
.chezmoi.<fmt>.tmpl (the config template) | Source-state root: ~/.local/share/chezmoi/.chezmoi.<fmt>.tmpl (.toml most common). Committed to the dotfiles repo. | You write it. Uses promptBoolOnce / promptChoiceOnce / promptStringOnce so a fresh machine generates the right config on first init. |
Override locations with --config <path> (use --config-format <fmt> if the extension is unusual). Look up effective values with chezmoi cat-config and chezmoi dump-config.
| User says | Go to |
|---|---|
| "set up chezmoi from scratch" / "new machine" | §2 Bootstrap |
| "add this file to my dotfiles" | §3 Add a file |
| "make this dotfile depend on OS / host" | references/templating.md |
| "encrypt this" / "store this token" | references/secrets.md |
| "run a script on first apply" | references/scripts.md |
| "chezmoi did something weird" | references/pitfalls.md |
| "design my full setup" | references/bootstrap.md |
The full one-liner that clones, renders templates, and applies in one shot:
sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply $GITHUB_USERNAMEFor a fully fleshed .chezmoi.toml.tmpl recipe with promptBoolOnce for work-vs-personal-vs-headless, see references/bootstrap.md.
chezmoi add ~/.zshrc # plain copy
chezmoi add --template ~/.gitconfig # add as a template (.tmpl suffix added)
chezmoi add --encrypt ~/.ssh/config # encrypted at rest
chezmoi chattr +template ~/.zshrc # turn an existing managed file into a template
chezmoi edit ~/.zshrc # edit the SOURCE — handles decrypt + .tmpl correctlyThen the safe-apply ritual (§5).
The table users get wrong most often. Prefixes must appear in a specific order, and the allowed prefix set depends on the target type — /reference/source-state-attributes/ is the canonical source.
| Prefix | Effect on target |
|---|---|
dot_ | Add leading . (dot_zshrc → ~/.zshrc) |
private_ | Strip group + world permissions (mode 0600 / 0700) |
readonly_ | Strip all write permission bits |
executable_ | Add executable bit |
empty_ | Allow empty file (default would remove it) |
encrypted_ | Decrypt source via age/gpg before writing target |
symlink_ | Create a symlink instead of a regular file (file form, target is symlink target) |
exact_ (dirs) | Delete anything in target dir not present in source |
create_ | Create target only if missing — never overwrite |
modify_ | Treat source as a script (or template, see below) that produces target contents |
run_ | Treat source as a script to execute (not a target file) |
before_ / after_ | Order scripts relative to applying files |
once_ (scripts) | Run only when contents have not been run successfully before |
onchange_ (scripts) | Run only when contents change for THIS filename |
remove_ | Remove the target if it exists |
external_ (dirs) | Mark a directory as an external — pairs with .chezmoiexternal.<fmt> / .chezmoiexternals/; chezmoi ignores attributes on child entries |
literal_ (anywhere) | Stop parsing further attributes from this point |
Allowed prefix order per target type (the order is rigid; rearranging is a parse error):
| Target type | Allowed prefixes (in order) | Optional suffix |
|---|---|---|
| Directory | remove_, external_, exact_, private_, readonly_, dot_ | — |
| Regular file | encrypted_, private_, readonly_, empty_, executable_, dot_ | .tmpl |
| Create file | create_, encrypted_, private_, readonly_, empty_, executable_, dot_ | .tmpl |
| Modify file | modify_, encrypted_, private_, readonly_, executable_, dot_ | .tmpl |
| Remove file | remove_, dot_ | — |
| Script | run_, once_ or onchange_, before_ or after_ | .tmpl |
| Symlink | symlink_, dot_ | .tmpl |
Suffix .tmpl makes the file a template. .age / .asc are stripped automatically when the corresponding encryption is configured.
Modify file detail. A modify_ source is a script by default — it reads the current target on stdin and writes the new target to stdout. If the file contains the string chezmoi:modify-template, lines containing that marker are stripped and the rest is interpreted as a template; the existing target is exposed in .chezmoi.stdin. Use the template form when you want to surgically edit a structured file (JSON, TOML, YAML); use the script form when you need a real interpreter (jq, awk, sed).
Before any apply that touches important files, run the inspection trio:
chezmoi status # one line per changed file
chezmoi diff # unified diff of pending changes
chezmoi apply --dry-run -v # full plan, including scripts that would run
chezmoi apply # only after the above looks rightFor routine pulls on a machine you trust:
chezmoi update # = git pull --autostash --rebase + apply
chezmoi git pull -- --autostash --rebase && chezmoi diff # preview only — no applychezmoi edit ~/.zshrc # opens source in $EDITOR, handles .tmpl + encryption
chezmoi cd # subshell in the source directory (for git operations)
chezmoi re-add # refresh source from current target after editing target
chezmoi merge ~/.zshrc # 3-way merge target ↔ source ↔ destination
chezmoi merge-all # 3-way merge every file whose actual ≠ target (add --init to re-render config first)Editor resolution: edit.command → $VISUAL → $EDITOR → vi (or notepad.exe on Windows). chezmoi prints a warning if the editor returns in < edit.minDuration (default 1s) — that catches the "I opened the file but didn't actually edit it" case. Set to 0 to silence.
Never open ~/.local/share/chezmoi/encrypted_dot_ssh/... or *.tmpl files directly in your editor — the file you see isn't the file chezmoi sees.
chezmoi apply is deterministic. Each run does:
run_before_ scripts in ASCII order of target name.directories materialized by externals) are written before the files they contain. Externals are fetched/expanded during this phase.
run_after_ scripts in ASCII order.Implications:
run_before_ script must not read a file that an external willprovide — externals are applied in phase 3, not before.
create_alpha and modify_dot_beta, .beta is updated before alpha because .beta < alpha in ASCII.
run_before_ script that writes into either violates that assumption — behavior is undefined.
When chezmoi shouldn't own the whole file, pick a pattern based on who the file belongs to:
`modify_` — user owns the file, chezmoi stamps a block. The source reads the existing target on stdin and writes the new target on stdout.
Script form, modify_dot_zshrc:
#!/usr/bin/env bash
set -euo pipefail
# Strip any prior chezmoi block from the existing file …
awk '/# chezmoi:begin/{s=1;next} /# chezmoi:end/{s=0;next} !s'
# … then append the current managed block.
cat <<'EOF'
# chezmoi:begin
export EDITOR=nvim
alias g=git
# chezmoi:end
EOFTemplate form (for structured files), modify_dot_config_private_app.yaml.tmpl:
{{- /* chezmoi:modify-template */ -}}
{{- $cfg := dict -}}
{{- if .chezmoi.stdin -}}{{- $cfg = .chezmoi.stdin | fromYaml -}}{{- end -}}
{{- $_ := set $cfg "telemetry" false -}}
{{- $_ := set $cfg "user" (dict "email" .email) -}}
{{ toYaml $cfg }}The if .chezmoi.stdin guard handles the initial-apply case where the target doesn't exist yet. Use fromJsonc / fromToml / fromYaml + toPrettyJson / toToml / toYaml for the round-trip.
`.chezmoitemplates/` — chezmoi owns the file, fragments are shared. Reusable blocks live under .chezmoitemplates/, pulled into regular templates with {{ template "name" . }} (or includeTemplate "name" . when you need to pipe the result through a function):
.chezmoitemplates/zsh-prompt # one file
dot_zshrc.tmpl # {{ template "zsh-prompt" . }}
dot_config/fish/config.fish.tmpl # {{ template "zsh-prompt" . }} tooChoose the pattern by ownership. modify_ when the user can freely edit the file outside the managed block. .chezmoitemplates/ when chezmoi renders the whole file but you want to DRY shared sections. Mixing both in one target is usually a smell.
These are non-negotiable. Each one corresponds to a real foot-gun:
get forked, get logged, get backed up. Pick a backend from references/secrets.md.
chezmoi edit $TARGET so the decrypt and template round-trips happen.
chezmoi diff or chezmoi apply --dry-run -v. exact_ on a directory deletes unmanaged entries; you want to see that coming.
(.chezmoi.toml.tmpl). Using them in regular dotfile templates causes a prompt on every apply / diff / status, which defeats automation.
Full details in references/secrets.md. Pick exactly one per repo.
functions. Secrets fetched at apply time, never on disk.
encrypted_ prefix). Identity at~/.config/chezmoi/key.txt, lives outside the repo.
.chezmoidata/secrets.yaml..chezmoidata/local.yaml(transitional, not a long-term answer).
Mixing backends multiplies moving parts without making anything more secure.
Full details in references/templating.md.
{{- if eq .chezmoi.os "darwin" }}
export HOMEBREW_PREFIX="/opt/homebrew"
{{- else if eq .chezmoi.os "linux" }}
export PATH="/usr/local/bin:$PATH"
{{- end }}
{{- if and (eq .chezmoi.os "darwin") (lookPath "brew") }}
# brew is installed
{{- end }}
email = {{ .email | quote }}Built-in template variables include .chezmoi.os, .chezmoi.arch, .chezmoi.hostname, .chezmoi.username, .chezmoi.homeDir, .chezmoi.sourceDir. User-defined data lives under [data] in chezmoi.toml, or any .chezmoidata/*.{toml,yaml,json} file.
chezmoi runs templates with Go's missingkey=error option, so a typo like {{ .gitub.user }} fails the apply instead of rendering empty. Override via [template] options = ["missingkey=zero"] only if you have a concrete reason — losing the typo guard is rarely worth it.
For symlink targets, leading/trailing whitespace in the rendered result is stripped, and an empty result removes the symlink. For file targets, an empty render removes the file (use the empty_ prefix to keep it).
All entries in the source dir beginning with . are ignored except the specials below. They are evaluated in this order on every operation: .chezmoiroot → .chezmoi.<fmt>.tmpl → data files → .chezmoitemplates/ → .chezmoiignore → .chezmoiremove → externals → .chezmoiversion.
| Path | Role |
|---|---|
.chezmoiroot | Single-line file pointing at a sub-directory as the source root |
.chezmoi.<fmt>.tmpl | Config-file template used on first chezmoi init (and on --init) |
.chezmoidata.<fmt> / .chezmoidata/ | Data merged into the template . namespace before any template renders |
.chezmoitemplates/ | Reusable template fragments ({{ template "name" . }}) — no target files |
.chezmoiscripts/ | Scripts not bound to a target file; honor run_before_ / run_after_ |
.chezmoiignore | Per-target ignore list — source-relative, templatable |
.chezmoiremove | Targets to remove on apply — templatable |
.chezmoiexternal.<fmt> / .chezmoiexternals/ | Pull files/archives from URLs at apply time |
.chezmoiversion | Min chezmoi version required by this source repo |
.chezmoiignore matches source-relative paths (e.g. dot_zshrc), not target paths. People get this wrong constantly.
<fmt> means toml, yaml, json, or jsonc. The directory forms (.chezmoidata/, .chezmoiexternals/) merge their contents in lexical order with any same-prefix file forms — useful for splitting big config across files or composing externals per role.
chezmoi apply~/.local/share/chezmoi/ directly when they are templatesor encrypted
.env / secrets.yaml to a "private" repochezmoi apply without first running chezmoi diff on a sharedmachine
prompt* template functions outside of .chezmoi.toml.tmplwithout a clear, written reason
fork/exec ...: exec format error running a templated script means anewline before #!. Add - inside the closing }} on the first template line to suppress it.
fork/exec ...: permission denied for scripts means $TMPDIR ismounted noexec. Set scriptTempDir in chezmoi.toml.
Linux/macOS. Add * text eol=lf to a .gitattributes in the dotfiles repo, or set core.autocrlf = input.
chezmoi init on a new machine before .chezmoi.toml.tmpl existssilently skips per-machine prompts. Land that template before adding host-specific files.
chezmoi doctor is the first command to run when something is off — itreports missing dependencies (op, age, etc.), broken paths, and shell integration issues in one pass.
chezmoi help and chezmoi help <command>; fall back to that.
chezmoi.toml and chezmoi.yaml) in~/.config/chezmoi/ is an error, not "first one wins". When migrating formats, delete the old file in the same commit.
--config-formatif you pipe a config from an unusual path.
chezmoi <unknown> looks for chezmoi-<unknown> on $PATH (git-styleplugin convention) before failing. Useful for adding repo-local helpers; surprising when a typo silently runs an unrelated binary.
The chezmoi docs evolve fast — new template functions, new config keys, new prefixes every few releases. When a question goes deeper than this skill, fetch the live page rather than guessing from training data. Prefer mcp__tavily__tavily_extract (single URL, extract_depth: "advanced", format: "markdown") — it returns the canonical /reference/* content directly. Use Context7 only as a fallback. Do not tavily_map or crawl — the URLs below are stable; pick the exact one.
Example call shape:
mcp__tavily__tavily_extract(
urls: ["https://www.chezmoi.io/reference/templates/functions/jq/"],
extract_depth: "advanced",
format: "markdown",
)Map of what lives where:
| Topic | URL |
|---|---|
| Concepts (source / target / destination / working tree) | /reference/concepts/ |
| Source-state attribute order per target type | /reference/source-state-attributes/ |
| Target types (file / dir / symlink / script / modify / create / remove) | /reference/target-types/ |
Application order on chezmoi apply | /reference/application-order/ |
| All command-line flags (global / common / developer) | /reference/command-line-flags/ |
Per-command reference (chezmoi <name>) | /reference/commands/<name>/ |
| Configuration file structure | /reference/configuration-file/ |
| Hooks (read-source-state / apply / etc.) | /reference/configuration-file/hooks/ |
| Interpreters for scripts by extension | /reference/configuration-file/interpreters/ |
Special files (.chezmoiignore etc.) | /reference/special-files/<name>/ |
Special directories (.chezmoidata/ etc.) | /reference/special-directories/<name>/ |
Template variables (.chezmoi.os etc.) | /reference/templates/variables/ |
Template directives (chezmoi:template:, etc.) | /reference/templates/directives/ |
| Built-in template functions | /reference/templates/functions/<name>/ |
init template functions (promptBoolOnce etc.) | /reference/templates/init-functions/<name>/ |
| Password-manager template functions | /reference/templates/<vendor>-functions/<name>/ (1password, bitwarden, dashlane, doppler, ejson, gopass, keepassxc, keeper, keyring, lastpass, pass, passhole, protonpass, vault, aws-secrets-manager, azure-key-vault, secret) |
| GitHub template functions | /reference/templates/github-functions/<name>/ |
| Plugins | /reference/plugins/ |
Heuristic: when the user asks "what does prefix X do" → source-state- attributes; "what does chezmoi <cmd> do" → commands/<cmd>; "what template var holds Y" → templates/variables; "how do I read a secret from Z" → templates/<vendor>-functions. Fetch only the page you need with mcp__tavily__tavily_extract — single URL, advanced depth — so the answer is always grounded in the canonical docs as of today, not what this skill happened to know when it was last edited.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.