Chezmoi Assistant
Expert assistant for chezmoi dotfiles management. Use when the user is managing dotfiles with chezmoi: adding files, creating templates, encrypting secrets, writing run scripts, syncing across machines, or diagnosing why changes aren't applying. Trigger phrases: 'add to chezmoi', 'make a template', 'chezmoi apply', 'encrypt with chezmoi', 'run script on first apply', 'sync dotfiles to new machine', 'chezmoi diff shows unexpected changes', 'source attribute', 'dot_ prefix', 'once_ script'.
npx skills add github:pantheon-org/tekhne --skill specialized--chezmoi--chezmoi-assistantYou are an expert in chezmoi, the multi-machine dotfiles manager. You help users track, template, encrypt, and sync their dotfiles using chezmoi’s source state model.
Mindset
The source directory is the single source of truth; everything under $HOME is a build artifact derived from it. Treat target files the way you’d treat a dist/ directory produced by a compiler — never hand-edit ~/.zshrc expecting the change to survive, because the next chezmoi apply overwrites it from source without asking. Every “why didn’t my change stick” question has the same first diagnostic step: check which side of the source/target boundary the edit landed on.
This also means changes flow one direction during normal operation: source → target via apply, and target → source only deliberately via chezmoi add or chezmoi re-add. Mixing those directions casually is how source state and reality drift apart.
When to Use
- The user is adding, templating, encrypting, or syncing dotfiles specifically through chezmoi (not editing shell/editor config in the abstract)
- Diagnosing why
chezmoi applydidn’t produce the expected target file, permissions, or content - Bootstrapping chezmoi on a new machine, or writing
run_scripts
When NOT to Use
- General shell, editor, or tool configuration questions with no chezmoi involvement — answer those directly rather than routing through chezmoi mechanics
- Other dotfile managers (GNU Stow, yadm, a bare git repo in
$HOME) — their models don’t share chezmoi’s filename-encodes-behaviour convention, so this skill’s specifics don’t transfer
Mental Model
chezmoi maps a source directory (~/.local/share/chezmoi) to a target directory (usually $HOME). Filenames in the source directory encode behaviour through prefixes and suffixes — they are never the literal target filenames.
Source: dot_gitconfig.tmpl → Target: ~/.gitconfig (template rendered)
Source: private_dot_ssh/ → Target: ~/.ssh/ (mode 700)
Source: run_once_setup.sh → Target: (executed once, not copied)
Daily Workflow
| Goal | Command |
|---|---|
| Track a file | chezmoi add ~/.zshrc |
| Edit tracked file | chezmoi edit ~/.zshrc |
| Preview changes | chezmoi diff |
| Apply to home | chezmoi apply |
| Edit + apply | chezmoi edit --apply ~/.zshrc |
| Open source dir | chezmoi cd |
| Check what would change | chezmoi status |
| Debug problems | chezmoi doctor |
Source State Attributes
See references/source-attrs.md for the full table. Key ones:
| Prefix | Effect |
|---|---|
dot_ |
Maps to dotfile — dot_zshrc → .zshrc |
private_ |
chmod 600/700 on target |
executable_ |
chmod +x on target |
encrypted_ |
Stored encrypted; decrypted on apply |
run_ |
Executed as a script, not copied |
run_once_ |
Script runs only if it has never run before |
run_onchange_ |
Script runs if its content changes |
before_ / after_ |
Script timing relative to other changes |
exact_ |
Removes unmanaged files from target dir |
create_ |
Creates file if absent; never overwrites |
modify_ |
Script receives current file content on stdin |
Suffix .tmpl → chezmoi renders the file as a Go template before writing.
Prefix order matters. Correct: run_once_before_ — not before_run_once_.
Templates
Use templates for machine-specific or secret values. Variables come from chezmoi data.
{{ .chezmoi.hostname }} — current hostname
{{ .chezmoi.os }} — "linux", "darwin", "windows"
{{ .chezmoi.arch }} — "amd64", "arm64"
{{ .chezmoi.username }} — current user
Conditional blocks:
{{- if eq .chezmoi.os "darwin" }}
export BROWSER=open
{{- else }}
export BROWSER=xdg-open
{{- end }}
Secret from password manager (e.g. 1Password):
export GITHUB_TOKEN="{{ onepasswordRead "Private" "GitHub" "token" }}"
Debug templates without applying: chezmoi execute-template < ~/.local/share/chezmoi/dot_zshrc.tmpl
Multi-Machine Setup
New machine bootstrap:
chezmoi init --apply $GITHUB_USERNAME
Daily sync:
chezmoi update # git pull + chezmoi apply
Push changes back:
chezmoi cd
git add -A && git commit -m "feat: update zsh config" && git push
Run Scripts
run_once_before_install-packages.sh — runs once, before apply
run_onchange_after_reload-shell.sh — reruns if script content changes
Scripts receive no target file — they are executed, not copied. Use run_once_ for bootstrapping, run_onchange_ for idempotent config reloads.
Troubleshooting Workflow
chezmoi doctor— check for common problems firstchezmoi diff— see what would changechezmoi status— quick summary (A=add, D=delete, M=modify)chezmoi cat ~/.zshrc— preview rendered target without applyingchezmoi data— inspect available template variables
Anti-Patterns
NEVER manually rename or mv files inside the source directory
WHY: chezmoi encodes target path, permissions, template status, and encryption entirely in the filename. A plain rename changes what’s on disk but not chezmoi’s understanding of prefix ordering, so a later chezmoi apply can target the wrong path or misinterpret the attributes.
❌ BAD:
mv dot_zshrc.tmpl dot_bashrc.tmpl
✅ GOOD:
chezmoi chattr template dot_bashrc # let chezmoi rewrite attributes safely
Consequence: Silent drift between the source state chezmoi believes it manages and what’s actually on disk — chezmoi diff starts reporting changes that were already applied, or an attribute-order-dependent script fires unexpectedly.
NEVER store plaintext secrets in source state without encrypted_
WHY: The source directory is routinely pushed to a personal git remote, and private_ only sets file permissions on the target machine — it does nothing to protect what’s sitting in git history.
❌ BAD:
private_dot_aws/credentials
✅ GOOD:
encrypted_private_dot_aws/credentials.asc
Consequence: Anyone who can read the repo’s git history — including a “private” repo later forked, mirrored, or exposed by a visibility mistake — has the plaintext secret forever, even after a later commit deletes it.
NEVER apply exact_ to the home directory itself
WHY: exact_ removes any target-directory file that isn’t declared in source state. Applied at the top level, “not declared” covers almost everything a user has in $HOME.
❌ BAD:
exact_. # top-level target = $HOME itself
✅ GOOD:
exact_dot_config/nvim/ # scoped to one subtree you fully manage
Consequence: chezmoi apply deletes every unmanaged file under the exact_ target with no confirmation beyond the standard diff — including files the user never intended chezmoi to touch.
NEVER commit chezmoi.toml with literal secret values
WHY: chezmoi.toml lives in the source directory alongside the templates it configures, so anything written there ships — and is committed — to every machine chezmoi syncs to.
❌ BAD:
[data]
githubToken = "ghp_abcd1234efgh5678"
✅ GOOD:
[data]
githubToken = {{ onepasswordRead "Private" "GitHub" "token" | quote }}
Consequence: The token is readable in plaintext in git history by anyone with repo access, even after a follow-up commit removes it from the working tree.
Eval Scenarios
- Scenario 1: Track and template a config file
- Scenario 2: Bootstrap a new machine
- Scenario 3: Write a run-once setup script
- Scenario 4: Add secrets to chezmoi safely
- Scenario 5: Rename a tracked file and scope
exact_correctly
References
references/commands.md— Full command referencereferences/source-attrs.md— All prefixes/suffixes with ordering rulesreferences/templates.md— Template variables, functions, and directives
Explicit NEVER/WHY/consequence triplets with side-by-side bad/good examples. Generic warnings score low — concrete failure modes with real impact (security, correctness, performance) score high.
Runtime validation via eval scenarios proving the skill actually changes agent behaviour. Requires evals/ directory, instructions.json, ≥3 scenarios, and ≥80% instruction coverage in summary.json.
Appropriate rigidity for the skill type: strict rules for critical/security skills, balanced steps for process skills, flexible options for tool skills. Mismatched calibration (too rigid or too loose) reduces score.
Expert-only knowledge the AI couldn't derive on its own — domain-specific patterns, production gotchas, decision frameworks, and non-obvious tool behaviour. High scores mean <5% redundancy with what the model already knows.
A clear philosophy plus numbered step-by-step workflows with explicit entry/exit points. Should answer: what is the core principle, how to execute it, and when (and when not) to apply it.
Rich activation keywords and trigger scenarios in the description so the skill fires when needed. Best descriptions read like an exhaustive list of the tasks and concepts that should invoke this skill.
Copy-paste-ready code, complete runnable examples, real file paths and commands. Pseudocode and abstract descriptions score low — concrete, executable content scores high.
SKILL.md as a <100-line navigation hub pointing to focused references/ files. Content should be lazy-loaded on demand, not front-loaded. Includes actionable 'When to Use' conditions per reference.
Proper frontmatter, single-task focus, activation keywords, and cross-harness portability. Skills must work across 40+ agentic harnesses without hardcoded paths or agent-specific references.
No eval scenarios available.
Chezmoi Command Reference
Getting Started
chezmoi doctor # check for common problems
chezmoi init # create source dir + git repo
chezmoi init $GITHUB_USERNAME # clone dotfiles from GitHub
chezmoi init --apply $GITHUB_USERNAME # clone + apply immediately
Daily Commands
chezmoi add ~/.zshrc # start tracking a file
chezmoi add --template ~/.zshrc # track as template
chezmoi edit ~/.zshrc # open source file in $EDITOR
chezmoi edit --apply ~/.zshrc # edit then apply immediately
chezmoi status # summary of pending changes (A/D/M)
chezmoi diff # show what apply would change
chezmoi apply # apply all pending changes
chezmoi apply ~/.zshrc # apply one file
chezmoi cd # open shell in source directory
Inspection & Debugging
chezmoi cat ~/.zshrc # print rendered target (no apply)
chezmoi data # print available template variables as JSON
chezmoi execute-template # render a template from stdin
chezmoi source-path ~/.zshrc # show source path for a target file
chezmoi target-path # show target path for a source file
chezmoi managed # list all managed files
chezmoi unmanaged # list unmanaged files in target
chezmoi doctor # diagnose environment problems
Attribute Management
chezmoi chattr +template ~/.zshrc
chezmoi chattr +encrypted ~/.netrc
chezmoi chattr +executable ~/.local/bin/script
chezmoi chattr -private ~/.config/foo
Multi-Machine Sync
chezmoi update # git pull + apply
# Commit and push changes:
chezmoi cd && git add -A && git commit -m "..." && git push
Encryption
# With age (recommended):
chezmoi add --encrypt ~/.ssh/id_rsa
# Configure in chezmoi.toml:
# [age]
# identity = "~/.config/chezmoi/key.txt"
# recipient = "age1..."
Status Output Key
| Symbol | Meaning |
|---|---|
A |
File will be added |
D |
File will be deleted |
M |
File will be modified |
R |
File will be run (script) |
Source State Attributes
File Prefixes (in order)
| Prefix | Effect |
|---|---|
after_ |
Run script after updating the destination |
before_ |
Run script before updating the destination |
create_ |
Ensure file exists; create with contents if absent (never overwrites) |
dot_ |
Rename to use a leading dot — dot_foo → .foo |
empty_ |
Ensure file exists even if empty (by default empty files are removed) |
encrypted_ |
Encrypt file in source state; decrypt on apply |
exact_ |
Remove anything in the target dir not managed by chezmoi |
executable_ |
Add executable permissions to target |
external_ |
Ignore attributes in child entries |
literal_ |
Stop parsing further prefix attributes |
modify_ |
Treat contents as a script that modifies an existing file (receives current file on stdin) |
once_ |
Only run the script if its contents have never run before |
onchange_ |
Only run if content has changed since last run |
private_ |
Remove all group and world permissions (chmod 600/700) |
readonly_ |
Remove all write permissions |
remove_ |
Remove the file/symlink/empty-dir if it exists |
run_ |
Treat contents as a script to run (not copied to target) |
symlink_ |
Create a symlink instead of a regular file |
File Suffixes
| Suffix | Effect |
|---|---|
.tmpl |
Render contents as a Go template before applying |
.literal |
Stop parsing further suffix attributes |
Ordering Rules by Target Type
| Target type | Allowed prefixes (in order) | Allowed suffixes |
|---|---|---|
| Directory | remove_, external_, exact_, private_, readonly_, dot_ |
none |
| 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_ |
none |
| Script | run_, then once_ or onchange_, then before_ or after_ |
.tmpl |
| Symlink | symlink_, dot_ |
.tmpl |
Common Examples
dot_zshrc → ~/.zshrc
dot_zshrc.tmpl → ~/.zshrc (template rendered)
private_dot_ssh/ → ~/.ssh/ (mode 700)
executable_dot_local/bin/my-script → ~/.local/bin/my-script (chmod +x)
encrypted_private_dot_netrc → ~/.netrc (encrypted + chmod 600)
run_once_before_install-homebrew.sh → executed once before apply
run_onchange_after_reload-launchd.sh → executed when script changes
exact_dot_config/fish/ → ~/.config/fish/ (removes unmanaged files)
create_dot_hushlogin → ~/.hushlogin (created if absent, never overwritten)
Changing Attributes
Use chezmoi chattr — do not rename files manually:
chezmoi chattr +template ~/.zshrc # add .tmpl suffix
chezmoi chattr +encrypted ~/.netrc # add encrypted_ prefix
chezmoi chattr +executable ~/.local/bin/myscript
chezmoi chattr -private ~/.config/some/file
Chezmoi Templates Reference
chezmoi uses Go’s text/template with missingkey=error by default (missing keys raise an error).
Built-in Variables
{{ .chezmoi.hostname }} — machine hostname
{{ .chezmoi.os }} — "linux", "darwin", "windows"
{{ .chezmoi.arch }} — "amd64", "arm64"
{{ .chezmoi.username }} — current user
{{ .chezmoi.homeDir }} — home directory path
{{ .chezmoi.sourceDir }} — chezmoi source directory path
{{ .chezmoi.group }} — current user's primary group
{{ .chezmoi.kernel.osRelease.id }} — e.g. "ubuntu", "fedora" (Linux)
Inspect all variables: chezmoi data
Conditionals
{{- if eq .chezmoi.os "darwin" }}
export BROWSER=open
{{- else if eq .chezmoi.os "linux" }}
export BROWSER=xdg-open
{{- end }}
{{- if eq .chezmoi.hostname "work-laptop" }}
export HTTP_PROXY=http://proxy.corp:3128
{{- end }}
Custom Data
Add to ~/.config/chezmoi/chezmoi.toml:
[data]
email = "alice@example.com"
workMachine = true
Use in templates: {{ .email }}, {{ .workMachine }}
Password Manager Functions
# 1Password
{{ onepasswordRead "vault" "item" "field" }}
# Bitwarden
{{ bitwarden "item" "field" }}
# LastPass
{{ lastpass "item" "field" }}
# gopass
{{ gopass "path/to/secret" }}
# keyring (macOS Keychain / libsecret)
{{ keyring "service" "username" }}
Sprig Functions
chezmoi includes the Sprig template library (see chezmoi.io/reference/templates/functions/ for the full list):
{{ "hello" | upper }} → HELLO
{{ list "a" "b" "c" | join "," }} → a,b,c
{{ env "HOME" }} → /home/alice
{{ include "other-file" . }} → render another template file
Debugging Templates
# Preview rendered output without applying
chezmoi cat ~/.zshrc
# Render from stdin
echo '{{ .chezmoi.os }}' | chezmoi execute-template
# Render a specific source file
chezmoi execute-template < ~/.local/share/chezmoi/dot_zshrc.tmpl
Template Options
Override missingkey=error in chezmoi.toml:
[template]
options = ["missingkey=zero"]
Whitespace Control
Use - to strip whitespace:
{{- if eq .chezmoi.os "darwin" -}}
...
{{- end -}}