This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Personal dotfiles managed by chezmoi. The working tree at ${XDG_DATA_HOME}/chezmoi (i.e. ~/.local/share/chezmoi) is chezmoi's source state; running chezmoi apply materializes it into ${HOME} (and a few system paths) on the target machine.
User normally accesses this repo via ${DOTFILES_DIR}, which is a symlink to ${XDG_DATA_HOME}/chezmoi. Both paths resolve to the same source tree — use either. Prefer ${DOTFILES_DIR} in user-facing references; resolve to the real path only when a tool requires a canonical location.
Filenames in this repo are not literal paths — they encode target-state metadata. Understand the prefixes before editing or adding files:
dot_foo→~/.foo(leading dot in target)private_foo→ mode0600(or0700for dirs)exact_foo/→ directory contents managed exactly: chezmoi removes unmanaged entries on apply. Applies only to files directly inside the dir, not recursively — subdirectories need their ownexact_prefix to get the same treatmentsymlink_foo→ target is a symlink; file contents are the link targetencrypted_foo→ decrypted on apply via age (key at~/.keys/age.key)*.tmpl→ Go-template rendered with chezmoi data (host facts,is_personal,is_work, etc.)run_once_before_NN-*.sh/run_once_after_NN-*.sh→ chezmoi script hooks, ordered byNN. Re-run only when script content hash changes — not on every apply. Sibling forms:run_onchange_*(re-run when content changes, no once-guard),run_*(every apply)remove_dot_foo→ ensures~/.foodoes NOT exist on target
Prefixes compose: private_dot_ssh, encrypted_private_profile-local-personal.sh.age, symlink_dot_profile-local.tmpl, etc. When renaming or moving a managed file, the prefix(es) must be preserved or the target-state semantics change.
.chezmoi.toml.tmpl derives boolean facts from chezmoi.hostname:
redstar/bluestar→is_personal = truesilverstar→is_work = true- anything else →
is_server = true
is_desktop / is_laptop / is_wtf / is_io are also exposed. Templates in this repo gate config blocks on these flags — do not hardcode hostnames in new content; reuse the booleans from .chezmoi.toml.tmpl data.
Canonical source of truth: .chezmoidata.yaml (hostnames, paths, age public key, weather city). Chezmoi loads this automatically; values are available in any .tmpl as .hostnames.*, .paths.*, .age.*, .weather.*.
Shell consumers get the same values via dot_config/profile.sh.tmpl, which renders export FOO=... lines from .chezmoidata.yaml and lands at ${XDG_CONFIG_HOME}/profile.sh on target. Both shell and chezmoi templates therefore share one source — no need to source profile.sh before chezmoi init on a fresh machine.
Paths in .chezmoidata.yaml are home-relative (no leading slash). Join with .chezmoi.homeDir when an absolute path is needed.
Conditional exports in profile.sh.tmpl — EDITOR, VISUAL, PAGER, MANPAGER, FILE_MANAGER, TAILNET_IP, TAILNET_CIDR, TERM, etc. — that are gated on __executable_exists / case / runtime probes are not in chezmoidata; they're shell-only and not meant for cross-file reuse.
When a config file or script references a path or hostname covered by these vars, prefer (in order):
- Literal env var inside the file (
${XDG_CONFIG_HOME}/foo/bar) — only if the consuming tool/parser expands env vars in that field. Verify with the tool's docs before using. - Chezmoi template — rename file to
.tmpland use{{ .chezmoi.homeDir }}/{{ .paths.xdg_config_home }}/foo/bar(or.hostnames.*,.age.*, etc.). Chezmoi resolves at apply time; the rendered file contains a literal absolute path. - Hardcoded path — only when neither option above is viable.
Never use {{ env "FOO" }} for these vars — env reads the user's current shell, which may not have profile.sh sourced (e.g. fresh-machine chezmoi init). Use chezmoidata instead.
If a new shared fact is needed, add it to .chezmoidata.yaml and (if shell needs it) profile.sh.tmpl — do not introduce one without the other.
Beyond the custom is_* booleans, .tmpl files have access to chezmoi's built-in template data and funcs:
.chezmoi.hostname— short hostname.chezmoi.os—linux,darwin, etc..chezmoi.arch—amd64,arm64, etc..chezmoi.homeDir— target user's${HOME}.chezmoi.sourceDir— absolute path to this repo's source state.chezmoi.username— target userenv "FOO"— read env var at render timeoutput "cmd" "arg"...— capture command stdout
Full reference: https://www.chezmoi.io/reference/templates/variables/
.chezmoi.toml.tmpl— config rendered into~/.config/chezmoi/chezmoi.tomlon init; defines host facts and age encryption identity/recipient.chezmoiexternal.toml.tmpl— external resources (files / git-repos / archives) chezmoi fetches and places under target paths;refreshPeriodcontrols TTL. Cache lives at~/.cache/chezmoi/;chezmoi apply --refresh-externalsforces refetch.chezmoiignore— paths in source state that should NOT be applied (README.md,TODO,age.key.ENCRYPTED).chezmoiscripts/— host-bootstrap scripts (package install, key fetch, post-apply URL rewrite).run_once_before_00-install-packages.shis the entry point that installsage curl git jq openssh-clientviaapt/dnf/pacmanexact_dot_etc/— config files that may be symlinked into/etc;find-etc-symlinksreports the link sites
Never edit target files (e.g. ~/.bashrc) directly with a text editor — next chezmoi apply overwrites the hand-edit. Two correct paths:
chezmoi edit <target>— opens the source file (resolves prefixes/template for you), reapplies on save.- Edit the source path under
${DOTFILES_DIR}directly, thenchezmoi apply.
If a target was already hand-edited and the change should be kept: chezmoi re-add <target> pulls live target content back into source state. Use chezmoi diff first to see what would be captured.
Apply changes to ${HOME}:
chezmoi applyDiff source state against target:
chezmoi diffRe-edit a managed file (opens the source, not the target):
chezmoi edit ~/.bashrcAdd a new managed file from the live target:
chezmoi add ~/.config/foo/bar.confPull from git remote and apply in one step:
chezmoi updateCheck for drift between source and target (exit code reflects status):
chezmoi verifySanity-check environment (binaries, config, encryption, externals):
chezmoi doctorTest a template render without applying:
chezmoi execute-template < some-file.tmplReset chezmoi state (per README):
rm -rf ~/.config/chezmoi ~/.local/share/chezmoiage is the encryption backend. Recipient and identity are pinned in .chezmoi.toml.tmpl. Encrypted files use the encrypted_ prefix and .age suffix. Do not commit plaintext copies of encrypted_* files.
private_dot_ssh/ holds SSH key material; entries are managed with private_ (mode 0600/0700) and, where applicable, encrypted_ via age. Never add plaintext private keys here — encrypt first.
Bootstrap scripts under .chezmoiscripts/ and any new shell helpers must follow the rules in the global config (${CLAUDE_CONFIG_DIR}/shared/rules/shell-scripts.md): #!/usr/bin/env bash, set -Eeuo pipefail, long CLI options, [[ ]] over [ ], quoted expansions, function name() syntax, etc. Existing scripts pre-date some of those rules — apply current rules to new code without mass-rewriting old code unless asked.
- Pick the correct prefix chain for the target path/mode/semantics.
- If the content varies by host, make it a
.tmpland gate onis_personal/is_work/is_server/ etc. - If it's a secret, use
encrypted_(andprivate_if mode matters). - If it should not be applied (docs, scratch), add it to
.chezmoiignore. - Run
chezmoi diffbeforechezmoi applyto confirm the rendered target matches intent.