pre-commit Hooks That Actually Help
Introduction¶
The pre-commit framework manages Git hooks in a
language-agnostic way: hooks are defined in .pre-commit-config.yaml, run in
isolated environments, and are shared across the team via the repo. It catches
trivial problems before they reach review or CI.
Setup¶
pipx install pre-commit # or: uv tool install pre-commit
pre-commit install # installs the git hook
pre-commit install --hook-type commit-msg # if you use commit-msg hooks
Now git commit runs the hooks on staged files. pre-commit run --all-files
runs everything (do this once when adding to an existing repo).
A useful config¶
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-json
- id: check-added-large-files # default 500kB; blocks accidental binaries
args: [--maxkb=1024]
- id: check-merge-conflict
- id: detect-private-key
- id: mixed-line-ending
args: [--fix=lf]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.0
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/gitleaks/gitleaks
rev: v8.21.0
hooks:
- id: gitleaks # secret scanning
- repo: https://github.com/rhysd/actionlint
rev: v1.7.4
hooks:
- id: actionlint # GitHub Actions workflow linting
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.42.0
hooks:
- id: markdownlint
args: [--fix]
Add per-stack: prettier/eslint (JS), gofmt/golangci-lint (Go),
terraform_fmt/terraform_validate (via antonbabenko/pre-commit-terraform),
shellcheck, hadolint (Dockerfiles).
Auto-fix vs fail¶
Hooks that modify files (formatters, end-of-file-fixer, ruff --fix) exit
non-zero when they change something — the commit is aborted, you git add the
fixes and commit again. This is intentional: you review what the tool changed.
Hooks that only check (gitleaks, check-yaml) fail with a message; you fix
and retry.
Run it in CI too¶
Local hooks can be skipped (git commit --no-verify). Enforce in CI:
# GitHub Actions
- uses: actions/setup-python@v5
- uses: pre-commit/[email protected]
# or, with caching, run `pre-commit run --all-files` yourself
Or use pre-commit.ci — a hosted service that runs the hooks on every PR and auto-commits fixes and hook version bumps.
Keeping it fast¶
- pre-commit runs hooks only on staged files by default — fast.
- Heavy hooks (type checkers, full test suites) don't belong in pre-commit; put
them in CI or a
pre-pushhook. pre-commit run --all-filesin CI is the slow one; cache~/.cache/pre-commit.pre-commit autoupdatebumpsrev:pins; run it periodically (or let pre-commit.ci / Renovate do it).
Verification and troubleshooting¶
pre-commit run --all-files
pre-commit run ruff --all-files # one hook
pre-commit run --files path/to/file
SKIP=gitleaks git commit ... # skip one hook for one commit
pre-commit clean && pre-commit gc # nuke cached hook envs
- Hook "passes locally, fails in CI" — different hook versions (CI installs
from
rev:, you have a stale cache), or the hook only checks staged files locally but--all-filesin CI finds pre-existing issues. Run--all-filesonce and fix the backlog. Executable not found/ language errors — the hook'slanguage:runtime (node, python, go, ruby) isn't available. pre-commit installs most itself;language: systemhooks need the tool on PATH.- Commit blocked by a formatter that keeps changing the same file — a
conflict between two formatters (e.g.
ruff-formatandblack, orprettierandeslint --fix). Pick one per file type. detect-private-key/gitleaksfalse positive on a test fixture — add a# pragma: allowlist secret(gitleaks) or anexclude:regex in the hook config; don't disable the hook.- Everyone uses
--no-verify— the hooks are too slow or too noisy. Trim to fast, high-signal checks; move the rest to CI. - Hook not running at all —
pre-commit installwasn't run in this clone, orcore.hooksPathis set elsewhere.pre-commit install -f.
Related tools and reading¶
- On-site: Regex Tester, YAML ↔ JSON converter.
- Related posts: Handling secrets in CI without leaking them, Git rebase vs merge.
Stuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.More in DevOps & CI/CD
OpenTelemetry for Distributed Tracing
A trace follows one request across services. OpenTelemetry is the vendor-neutral way to produce them — here's the model, the Collector, and sampling that won't bankrupt you.
October 24, 2026Chaos Engineering Basics
Chaos engineering is controlled experiments that inject failure to verify your system tolerates it. Here's the method, safe blast radius, starter experiments, and game days.
October 24, 2026Trunk-Based Development, Practically
One shared branch, small changes merged daily, features hidden behind flags until ready. Why most high-throughput teams moved off GitFlow.
October 24, 2026