pre-commit hooks: catch mistakes before they are committed
Install the pre-commit framework, configure hooks for whitespace, YAML and JSON syntax, large files, leaked private keys, Python linting and shellcheck, run them on every commit and in CI, and keep them updated.
Drafted with AI from official documentation. Scripts were run in a test environment and repository links verified. Awaiting a human check. Did it work for you? Last edited Outdated?
Git can run a script before each commit; the pre-commit framework makes those scripts shareable. One YAML file in the repository lists the checks, every developer runs pre-commit install once, and from then on trailing whitespace, broken YAML, an accidentally staged private key or a formatting slip is caught on the laptop in a second instead of in CI ten minutes later. The same configuration runs in CI as a safety net for people who skipped the hook.
Step 1: install the tool
pipx install pre-commit
pre-commit --version
Alternatives: brew install pre-commit on macOS, pip install pre-commit inside a project virtual environment, or your distribution’s package. It needs Python to run itself, but the hooks it manages can be written in any language; pre-commit installs each hook’s toolchain into an isolated environment under ~/.cache/pre-commit.
Step 2: the configuration file
In the root of your repository create .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-merge-conflict
- id: check-added-large-files
args: ["--maxkb=1000"]
- id: detect-private-key
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.0
hooks:
- id: ruff
args: ["--fix"]
- id: ruff-format
- repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.10.0.1
hooks:
- id: shellcheck
- Each
repois a Git repository that publishes hooks;revpins the exact tag so every developer runs the same version. pre-commit-hooksis the standard collection: whitespace and newline fixes, syntax checks for YAML and JSON, a guard against committing merge conflict markers, a size limit for new files, anddetect-private-key, which blocks files containingBEGIN ... PRIVATE KEY.rufflints and formats Python and fixes what it safely can; for other languages swap in the matching hook, for examplepre-commit/mirrors-eslintfor JavaScript orgolangci/golangci-lintfor Go.shellcheckcatches quoting and portability bugs in shell scripts.
The pinned revisions above will be behind by the time you read this. Run pre-commit autoupdate right after creating the file and it rewrites every rev to the newest tag.
Step 3: install the hook and run it on everything
pre-commit install
pre-commit run --all-files
install writes a small script to .git/hooks/pre-commit; the first run downloads the hook repositories, which takes a minute, and then checks every tracked file. Expect a wall of fixes on an older project: hooks like trailing-whitespace modify files and report Failed so you can review the changes with git diff, then stage and commit them as one cleanup commit.
Step 4: what a commit looks like now
git add .
git commit -m "Add user export"
trim trailing whitespace.................................................Passed
fix end of files.........................................................Passed
check yaml...............................................................Passed
check json...........................................(no files to check)Skipped
check for merge conflicts................................................Passed
check for added large files..............................................Passed
detect private key.......................................................Passed
ruff.....................................................................Failed
- hook id: ruff
- exit code: 1
app/export.py:12:8: F401 [*] `os` imported but unused
The commit is aborted. Fix the file (or let the hook fix it, if it can), git add it again and repeat the commit. Hooks only see the files that are staged, so a commit touching two files takes well under a second.
Skipping a hook when you must
SKIP=ruff git commit -m "WIP: refactor in progress"
git commit --no-verify -m "Emergency hotfix, lint follows"
SKIP disables named hooks for one commit; --no-verify disables all of them. Both are legitimate for a work-in-progress commit on a private branch. The CI job in the next step is what makes sure the skipped check is still applied before the code reaches main.
Step 5: run the same checks in CI
A GitHub Actions workflow that runs every hook on every file:
name: pre-commit
on:
pull_request:
push:
branches: [main]
jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- run: python -m pip install pre-commit
- run: pre-commit run --all-files --show-diff-on-failure
--show-diff-on-failure prints what the fixing hooks would have changed, so the author can see the problem from the CI log. For GitHub repositories the hosted service pre-commit.ci does the same and can push the fixes to the pull request automatically. On GitLab, the equivalent is a job with the same two commands in .gitlab-ci.yml.
Keeping it healthy
pre-commit autoupdateonce a month, in its own commit, so a hook upgrade that introduces new findings is easy to identify.pre-commit run ruff --all-filesruns a single hook; handy after changing its arguments.pre-commit cleanandpre-commit gcremove cached hook environments when the cache grows or a hook misbehaves.- Exclude generated files with a top-level
exclude: ^(dist/|vendor/)regular expression rather than disabling hooks. - Keep the hook set fast. Full test suites belong in CI; pre-commit is for checks that finish in seconds.
Official documentation
Did this work for you?
No votes yet. Be the first.
If it did not work, the report form below tells the editors what went wrong.
See also
- 0032 First-time Git setup: name, email, default branch, SSH or HTTPS Tell Git who you are, pick sensible defaults, and choose between SSH and HTTPS for GitHub. Includes a small script that applies… Beginner · Step by step · Linux, macOS, Windows Script Setup 15 Oct 2026
- 0033 The everyday Git workflow: status, add, commit, push, pull Git moves your changes through three places: the working folder, the staging area, and the commit history. These five commands, plus .gitignore… Beginner · Explains an idea · Any system Article Housekeeping · Setup 15 Oct 2026
- 0262 Make your first pull request: Git and GitHub from first commit to merged change A complete beginner's path through Git and GitHub: set up Git, make commits, ignore files, use branches, push to GitHub, open a… Beginner · Guided project · Any system Article Branching & rebasing · Pull requests · Setup 15 Oct 2026
- 0313 What is CI/CD? CI/CD is the habit of automatically testing every code change and, once it passes, deploying it without manual steps. Beginner · Explains an idea · Any system Article Pipelines, runners & hooks 15 Oct 2026
- 0318 Fix: fatal: not a git repository Git looked in the current directory and every parent for a .git folder and found none. You are in the wrong directory,… Beginner · Fixes an error · Any system Article Common errors · Setup 15 Oct 2026
- 0378 Continuous integration explained What continuous integration means, why small frequent merges plus an automated build on every change prevent integration pain, what a good pipeline… Beginner · Explains an idea · Any system Article Pipelines, runners & hooks 15 Oct 2026
Repos in the wild
- pre-commit: a framework for managing multi-language Git hooks pre-commit/pre-commit The pre-commit repository: the tool that installs, isolates and runs Git hooks declared in a YAML file, with hooks written in Python, Node, Go, Rust, Ruby, Docker and more, pinned to exact versions. GitHub
- Woodpecker CI: a lightweight, self-hosted CI engine with container-based pipelines woodpecker-ci/woodpecker The Woodpecker CI repository: a community fork of Drone that runs every pipeline step in its own container, connects to GitHub, Gitea, Forgejo, GitLab and Bitbucket, and runs on a single small server. GitHub
- Gitleaks: find secrets that were committed to a Git repository gitleaks/gitleaks Gitleaks scans Git history, working trees and arbitrary files for passwords, API keys, tokens and private keys using a library of patterns, and is usually run as a pre-commit hook and in CI. GitHub
- Semgrep: static analysis with rules you can read semgrep/semgrep Semgrep is a fast static analysis tool whose rules look like the code they match, with a community registry of thousands of rules for finding bug patterns and insecure code across more than thirty languages. GitHub
History
Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.
- Dominik +5,840 chars