Git hooks
Git fires scripts at deterministic points (pre-commit, commit-msg, pre-push, post-merge, …) and a blocked exit code vetoes the action. That’s the whole mechanism — everything else is how teams get the same hooks onto every clone and keep them fast. The core problem: .git/hooks/ is not versioned and not distributed — fresh clones start hookless. Every hook manager is an answer to “how do hooks travel with the repo?” plus “how do they stay fast enough that nobody types --no-verify?”.
The distribution mechanisms
- Bootstrap script /
prepareentrypoint (npm convention) — a package-manager lifecycle step clones/symlinks hooks on install. Simple, language-bound (husky’s route). - Single-binary config in repo root —
.pre-commit-config.yaml/lefthook.yml/ hk config; a binary installed once reads it and installs itself into the hooks path. Language-agnostic, the modern default. core.hooksPath— point git at a versioned directory in the repo (.githooks/+ one-line setup). Zero dependencies, transparent shell scripts; the KISS option. Underrated for simple needs.- Git ≥2.54 native config hooks (
[hook "x"]in gitconfig, 2026-04) — personal/global hooks without any manager; but.git/configisn’t versioned, so it doesn’t solve team distribution.
The managers (candidates, none chosen yet)
- prek (OSS) — Rust drop-in pre-commit replacement: same
.pre-commit-config.yaml, same hook registry, parallel execution, millisecond startup, built-in in-place fixes. The bet: keep pre-commit’s ecosystem, delete its Python cost. - pre-commit (Yelp) — the incumbent. Largest hook ecosystem, per-hook isolated venvs,
autoupdate. Cost: Python required, slow first run, per-hook-repo supply-chain surface (moving tags —--freezemitigates). - Husky — the JS standard: hooks as plain shell scripts in
.husky/, wired bynpm prepare. No parallelism or staged-file filtering without adding lint-staged. - Lefthook — Go single binary,
lefthook.yml, native parallel execution,{staged_files}glob filtering, local override files. The polyglot performance option. - hk (OSS) — the jdx (mise author) entry, Rust: linters/formatters run in parallel under read/write file locks (independent tools concurrent, same-file tools take turns), and the same steps run in git hooks, from the terminal, and in CI — one config, three contexts, killing the “hooks pass locally, CI checks something else” drift.
Measured reality (third-party benchmarks, 500+ files): lefthook warm <1s; husky+lint-staged 2-3s; pre-commit 4-6s (venv overhead). prek claims parity with pre-commit at lefthook-class speed; hk’s lock-based parallelism is the newest datapoint.
The open head-to-head: prek vs hk — prek bets on pre-commit config/ecosystem reuse, hk on hooks=CLI=CI unification. Decision pending hands-on.
Which hook, when (working position)
jyn.dev — pre-commit hooks are fundamentally broken (capture: clippings/jyn-dev-pre-commit-hooks-are-fundamentally-broken.md) argues pre-commit hooks are the wrong default: they gate your commit on your machine for things CI will check anyway, the slowness trains --no-verify, and hooks that mutate files mid-commit race with the user. Working position adopted here (KRZ): run hooks at pre-push, with one exception — credential scanning at pre-commit, because a secret that lands in a commit is in history forever; style/lint can wait for push, leaked tokens cannot. That also matches the “hooks may fix, one writer per pass” rule below.
Video companions (transcripts captured): “Git Will Finally Make Sense After This” (LearnThatStack — commits are snapshots, the DAG, refs as pointers: sources/Git Will Finally Make Sense After This.md) and Jessica Kerr’s “Git Happens” (JDD — vocabulary-first git model, why the man pages defeat newcomers: sources/Jessica Kerr Git Happens.md). “You’ve Been Using Git Wrong” (Tom Delalande) covers where CI hooks should run in the Git-vs-GitHub paradigm conflict (sources/You've Been Using Git Wrong.md).
Design rules (from the garden’s own hook + Cilium’s CI doctrine)
- Hook what the project actually cares about (the garden’s frontmatter check validates YAML contracts), not generic hygiene theater.
- A hook may fix (insert keys, bump
updated:) on staged files — but one writer per pass; two auto-fixers racing corrupts files. - Parallelism needs file locks or staged-file partitioning — otherwise linters and formatters fight over the same files (hk’s whole thesis).
- Supply-chain: every manager adds an install-time execution surface; pin hook repos to immutable SHAs (
--freeze), respect the cooldown logic from the Golden repo CI section. - CI parity is a feature: if the hook config and the CI lint job are different artifacts, they will drift — hk’s three-context unification (or just running the same script both places) is the fix.
- The
--no-verifyescape hatch is a pressure gauge: frequent bypasses mean hooks are too slow or too preachy — fix the hook, don’t blame the user.
Related: Golden repo (the rules this note feeds) · prek (OSS) · hk (OSS) · Diff tools (what a hook might run) · Agent tests (hooks as the first automated gate in the testing ladder) · Git recovery · Git commit messages · Git housekeeping · Git worktrees