Operations
Zsh Completion Cache Lifecycle
This page documents how zsh completion compilation works in this repo and how to force a rebuild. Managed under #864.
The Pieces
Three caches cooperate to keep compinit fast without compromising correctness:
-
The compinit dump —
$XDG_CACHE_HOME/zsh/zcompdump-$ZSH_VERSION. Built bycompinit -d <path>from the fpath corpus. Encodes which file provides each completion function. Per-zsh-version so an OS upgrade doesn't silently serve a stale binary index. -
The compinit dump bytecode —
<dump>.zwc. Built byzcompilein the background after the dump is rebuilt. Zsh prefers the.zwcform on subsequent shells, skipping re-parse. -
fpath bytecode siblings —
_<tool>.zwcnext to each_<tool>source file under~/.local/share/zsh/completions/. Built once per chezmoi apply byrun_onchange_after_zcompile-completions.sh.tmpl(so this happens outside the critical shell-start path). Zsh prefers the.zwcwhen autoloading the corresponding completion function.
Flow on Shell Start
zsh starts
→ sources rc.d (no compinit yet)
→ registers a `preexec` (or `precmd`) hook
→ returns to prompt
user presses Tab (or `precmd` fires)
→ `compinit -d $XDG_CACHE_HOME/zsh/zcompdump-$ZSH_VERSION`
or `compinit -C -d <dump>` if the dump is < 24 hours old
→ background `zcompile` of the dump
→ first tab-completion uses the precompiled `.zwc` for each tool
Flow on Chezmoi Apply
chezmoi apply
→ if completion sources changed, run_onchange fires
run_onchange_after_zcompile-completions.sh.tmpl
→ walks ~/.local/share/zsh/completions/
→ for each _<tool> file whose .zwc is missing or stale:
zsh -c "zcompile <file>"
→ reports compiled=N skipped=M
Configuration Surface
| Variable | Default | Purpose |
|---|---|---|
DOTFILES_ENABLE_COMPINIT | 1 on laptop/desktop profiles, 0 elsewhere | Whether to load the completion subsystem at all. Non-laptop profiles skip the runtime cost but still benefit from precompiled .zwc files if present. |
DOTFILES_DEFER_COMPINIT | 1 | When 1, compinit runs on first preexec. When 0, runs on precmd. |
DOTFILES_FAST | 0 | When 1, the entire completion path is skipped. |
DOTFILES_ULTRA_FAST | 0 | Same as DOTFILES_FAST but more aggressive. |
DOTFILES_SKIP_ZCOMPILE | 0 | When 1, the run_onchange hook does not precompile fpath files. Used in CI / minimal images where bytecode isn't needed. |
XDG_CACHE_HOME | $HOME/.cache | Standard XDG override; the dump path is $XDG_CACHE_HOME/zsh/zcompdump-$ZSH_VERSION. |
Force Rebuild
When completions go stale (e.g., after a tool upgrade):
# Invalidate the compinit dump — next shell rebuilds it.
# Force regeneration of fpath bytecode siblings:
Or, equivalently, dot heal (which calls both in sequence).
Measured Cost
The current dotfiles ship with compinit deferred to first-prompt, which means shell startup time is already nearly minimal — zsh doesn't parse the fpath corpus until tab-completion is invoked. The precompiled .zwc files cut down the first tab-completion latency per tool, not shell-start latency. On a 2026 M-series macOS host the P50 for time zsh -i -c exit measured at 39 ms before this change and 39 ms after — the change pays off when the user starts typing, not before.
This makes the lifecycle policy still worth shipping:
- Cleaner cache layout (XDG-cache, version-keyed).
- Bytecode siblings auto-picked on non-laptop profiles (which skip compinit entirely) so completion is still fast even without the full subsystem.
- A documented force-rebuild path.
Skip Rules
The run_onchange hook skips itself when:
DOTFILES_SKIP_ZCOMPILE=1(CI / minimal images).zshis not on PATH (servers without an interactive shell).~/.local/share/zsh/completions/doesn't exist.
System fpath directories (Homebrew, /usr/local/share/zsh) are intentionally NOT precompiled — the package manager already manages their lifecycle.
References
dot_config/zsh/rc.d/30-options.zsh.tmpl— the deferred-compinit logic.run_onchange_after_zcompile-completions.sh.tmpl— the apply-time precompile hook.zshcompletion docs.- ADR-002 (Shell Performance Optimization).
- Issue #864.