How to Use Pre-commit: The Beginner's Guide (First Install, the Config-File Lesson, First Commit, and the --no-verify Rule)
CodexFuse Team · 2026-11-01T22:52:01.997Z · 13 min read min read
Learn how to use pre-commit: install the framework, write .pre-commit-config.yaml, run your first hooks, and know when --no-verify is allowed.
There are two different things called "pre-commit", and this is the number-one source of confusion for everyone learning how to use pre-commit. Git ships with a built-in pre-commit hook: an executable script that lives in your repository's .git/hooks directory and runs whenever you commit. The pre-commit framework (from pre-commit.com) is a multi-language package manager for those hooks: instead of hand-writing and copying shell scripts between repositories, you declare which checks you want in a single .pre-commit-config.yaml file, and the framework installs, runs, and updates them for you. This pre-commit tutorial covers the framework, pinned to the current release (4.7.0), from install to your first passing commit. Think of it as the git hooks framework: shared, versioned, and team-wide, instead of hand-copied scripts.
If you are still choosing the Git client you will run all this through, read our GitButler beginner guide first — GitButler is the desktop client layer, pre-commit is the hook-framework layer, and they stack on top of each other rather than competing.
What Is Pre-commit, Exactly?
Pre-commit is a framework that manages Git hooks for you. A Git hook is a script that Git runs automatically at a specific moment — the pre-commit hook runs after you type git commit but before the commit is created. Writing those scripts by hand means every teammate ends up with a different, unshared set of checks. Pre-commit replaces that chaos with one version-controlled config file: the whole team gets the same hooks, the same versions, and the same behavior on every machine.
The framework is language-agnostic even though it is written in Python: pre-commit hooks for Python projects form its largest ecosystem, and it installs and runs hooks written in Node, Go, Ruby, Rust, Docker images, and plain system scripts just as easily. The hook's language is declared in the hook repository itself, and pre-commit builds an isolated environment for it. That is what makes it a package manager for hooks rather than just another linter.
How Do You Install the Pre-commit Framework?
You install pre-commit with your platform's Python tooling. The official method is pip install pre-commit, which you add to your project's requirements.txt or requirements-dev.txt so the whole team installs the same tool. The framework's GitHub releases page confirms 4.7.0 as the current version, published October 13, 2026, with Homebrew and conda-forge both packaging 4.7.0.
| Method | Command | When to use it |
|---|---|---|
| pip | pip install pre-commit | Inside a Python project's environment; pin it in requirements files |
| pipx | pipx install pre-commit | Isolated global install (works because the package is on PyPI; not the official docs' method) |
| Homebrew | brew install pre-commit | macOS or Linux without touching a Python environment |
| conda | conda install conda-forge::pre-commit | Anaconda/miniconda shops |
| Zero-dependency zipapp | Download pre-commit-4.7.0.pyz from GitHub releases | CI or machines where you want no Python dependency at all |
Verify the install with pre-commit --version, which the official quick-start shows printing pre-commit 4.7.0. If you want the conceptual foundation of the hooks this framework manages, our Git beginner guide covers how Git's native hooks work before any framework gets involved.
How Do You Write .pre-commit-config.yaml?
You declare your hooks in .pre-commit-config.yaml at the repository root. The file has three levels: a top-level repos list, each repo pointing at a Git URL and a pinned rev (a tag or revision to clone at), and each repo listing the hooks you want from it by their id. Everything else is optional refinement.
Do not hand-write your first config from memory — generate it. The command pre-commit sample-config prints a working starter configuration; redirect it into the file with pre-commit sample-config > .pre-commit-config.yaml. Then extend it, for example with the framework's own hook bundle:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
That bundle, the pre-commit-hooks repository, is at v6.0.0 as of this writing and gives you whitespace trimming, newline fixing, YAML validation, and a guard against accidentally committing huge files. It is the standard starting point for pre-commit hooks Python teams adopt first. Learn this anatomy table before you copy configs from other projects:
| Key | Where it lives | What it does |
|---|---|---|
repos | Top level | The list of hook repositories to use |
repo | Per repo entry | Git URL to clone, or local / meta for special hooks |
rev | Per repo entry | The tag or revision to check out — pin this, never float it |
hooks | Per repo entry | Which hooks to run, each with an id |
id | Per hook | The hook's name inside its repository |
files / exclude | Per hook or top level | Regex filters controlling which filenames the hook sees |
types / types_or | Per hook | File-type filters (for example only Python files); ANDed vs ORed |
args | Per hook | Extra arguments passed to the hook before the filenames |
stages | Per hook | Which Git stages trigger the hook (pre-commit, pre-push, and more) |
How Do You Activate the Hook in a Repository?
You activate the framework with pre-commit install — and the order matters: create the config file first, then run install. If you run install with no config present, the installed hook refuses to run commits until you pass --allow-missing-config, an escape hatch the ESP-IDF contributor docs confirm lets commits succeed in branches that have no config file yet. So the correct order is config first, install second.
Install is polite by default. If your repository already had hand-written hook scripts, pre-commit install runs in migration mode: it keeps your existing hooks and adds the framework alongside them. Pass -f (or --overwrite) to replace them instead. And pre-commit uninstall reverses the whole thing, restoring your previous hooks.
How to Use Pre-commit in a Real Repository
Run this six-step first-use arc once so you see every layer working:
- Install the framework (
pip install pre-commit) - Generate the config (
pre-commit sample-config > .pre-commit-config.yaml) - Edit the config to add the hooks your team wants
- Activate it (
pre-commit install) - Stage a file and commit — watch the hooks run
- Sweep history with
pre-commit run --all-files
What Happens When You Make Your First Commit?
Your hooks run only on the files you staged. This is the most important behavior in the whole framework: pre-commit runs hooks against the staged contents of your files, temporarily stashing your unstaged changes while the hooks execute. The official docs are explicit that running hooks on unstaged changes would produce both false positives and false negatives, so the stash is deliberate.
Expect the first run to be slow. Pre-commit downloads and builds an isolated environment for each hook's language the first time it runs — the output tells you so ("Initializing environment for ... This may take a few minutes..."), and every later run reuses that environment. A hook fails in one of two ways: it exits nonzero, or it modifies files. That second case is normal for formatter hooks: they show as Failed with the message "Files were modified by this hook", meaning the hook fixed your files and you simply stage the fixes and commit again.
You can also try a hook before committing to it in your config. pre-commit try-repo lets you run a hook repository against your files without editing .pre-commit-config.yaml — the try-before-you-buy path for evaluating new checks.
How Do You Run Hooks on Demand?
You run hooks outside of commits with pre-commit run. With no arguments it runs every hook against your staged files, which is the manual equivalent of the commit trigger. The variations are what make it useful in daily work.
pre-commit run --all-files # every hook, every file (also the CI invocation)
pre-commit run flake8 # one hook by its id, on staged files
pre-commit run --files app.py # every hook, but only these paths
pre-commit run --from-ref main --to-ref HEAD # hooks on the changed range
Use run --all-files when you first add pre-commit to an old repository — it sweeps the entire history of accumulated lint debt in one pass. Per-hook files filters keep heavy tools scoped: a Python-only hook should never see your Markdown files.
When Should You Skip a Hook, and What Is the --no-verify Rule?
You skip a single hook with the SKIP environment variable, never by reaching for --no-verify first. SKIP=flake8 git commit -m "wip" skips just the flake8 hook; git commit --no-verify (or -n) skips every hook. The distinction is the whole rule: SKIP is a scalpel, --no-verify is a sledgehammer.
Teams hate --no-verify because it silently disables the safety net the config exists to provide — including the secret-blocking hooks that stop you from committing credentials. The sane team rule is: skipping one named hook for one commit is a judgment call you document in the commit message; skipping all hooks is a red flag that should trigger a conversation, not become a habit. Note that the framework also honors SKIP as a comma-separated list, so SKIP=flake8,mypy skips both in one shot.
How Do You Keep Hook Versions Current?
You update pinned hook versions with pre-commit autoupdate. The rev you wrote in the config is a frozen tag — deliberately, because floating revisions break teams when a hook author pushes a breaking change at 2 a.m. Autoupdate rewrites those rev values to the latest tag on each hook repository's default branch, so you upgrade on your schedule, review the diff, and commit it like any other dependency bump.
Run autoupdate on a rhythm — monthly is the common cadence — and commit the result in its own change so a broken hook update is trivial to revert. The framework's cache of downloaded hook environments grows over time, so run pre-commit clean (removes cached files) and pre-commit gc (removes unused cached repos) periodically to keep your disk usage sane.
How Does Pre-commit Compare to Husky and Lefthook?
Pre-commit is the multi-language framework; Husky and Lefthook are single-ecosystem runners. The classic pre-commit vs Husky question is really about scope, not quality: if your project is JavaScript-only, Husky (Node) or Lefthook (fast, polyglot-ish but JS-centric in practice) sets up hooks with less machinery. If your repository mixes languages — Python backend, TypeScript frontend, Terraform for infra — pre-commit manages all of them from one config with isolated per-language environments, which is why polyglot teams standardize on it. Treat Husky and Lefthook as comparison members, not rivals: same lane, different breadth.
| pre-commit | Husky | Lefthook | |
|---|---|---|---|
| Languages | Multi-language (Python, Node, Go, Ruby, Rust, Docker, system, more) | Node/JavaScript ecosystem | Fast runner, JS-centric configs |
| Config | .pre-commit-config.yaml (shareable, versioned) | npm scripts + hook files | lefthook.yml |
| Hook isolation | Isolated environments per hook language | Runs in project environment | Runs in project environment |
| Version pinning | rev per hook repo + autoupdate | npm versioning | Manual / package manager |
| Best for | Polyglot repos and teams wanting one standard | JS-only projects | JS projects wanting speed |
How Do You Run Pre-commit in CI?
You run the same hooks in CI with pre-commit run --all-files. That command checks out the pinned hook versions from your config and runs every hook against every file — the identical checks developers get locally, enforced on the server. A pipeline step is usually just the install plus that one command.
The official GitHub Action is pre-commit/[email protected] (released February 2024), with an extra_args input for customizing the invocation. One caveat, stated plainly in the action's own README: the action is in maintenance-only mode, and the maintainers recommend pre-commit.ci — the hosted service that runs your config on every pull request without you managing runners. Our GitHub Actions beginner guide walks through pipeline setup if you are building the workflow from scratch.
7 Mistakes Beginners Make with Pre-commit
Every team hits these. They are all avoidable once you know the framework's model.
| Mistake | What actually happens | The fix |
|---|---|---|
| Installing the hook before writing the config | Commits error out because there is nothing configured | Config first, pre-commit install second — always |
| Expecting hooks to check unstaged work | Hooks pass on staged files while your unstaged edits hide bugs | Remember the stash: stage everything the hook should see |
| Panicking when a formatter "fails" | Failed — Files were modified by this hook looks like breakage | It fixed your files; stage them and commit again |
Leaving rev floating or never updating | Either breakage at 2 a.m. or years of stale checks | Pin revs, run pre-commit autoupdate monthly, commit the diff |
Using --no-verify as a habit | The whole safety net goes dark, including secret blockers | SKIP=hook-id for one hook, one commit, documented |
| Never cleaning the cache | Gigabytes of old hook environments accumulate silently | pre-commit clean and pre-commit gc periodically |
| Copying a config without reading the schema | Unknown keys and broken filters that "worked on their machine" | Learn the repos/rev/hooks anatomy; try-repo before committing to new hooks |
One last lane line before you go: this guide covers the hook framework that polices commits. If the version-control layer itself interests you — an alternative to Git's own CLI — read our Jujutsu beginner guide. Different layer, same commitment to cleaner history.
Sources
- pre-commit GitHub releases API — v4.7.0 tag, published October 13, 2026
- pre-commit.com official documentation — install methods, quick-start (sample-config, install, run --all-files), config schema, CLI reference, SKIP env var, --allow-missing-config, staged-files stash behavior, autoupdate, try-repo, uninstall and migration mode
- PyPI pre-commit project page — 4.7.0 wheel uploaded October 12, 2026
- Homebrew formula for pre-commit — brew install pre-commit, stable 4.7.0
- conda-forge pre-commit package — conda install conda-forge::pre-commit, 4.7.0
- pre-commit GitHub Action README — action usage template, extra_args input, maintenance-only status, pre-commit.ci recommendation
- pre-commit/action releases API — v3.0.1, published February 7, 2024
- pre-commit-hooks releases API — v6.0.0 tag, published August 9, 2025
- ESP-IDF contributor docs — practical effect of --allow-missing-config in branches without a config file
- Stefanie Molin's pre-commit workshop — the pre-commit-hook vs pre-commit-framework distinction, setup and run commands
- SQLServerCentral pre-commit guide — sample-config usage, running hooks without install
- DevOpsSchool pre-commit install guide — current 2026 install walkthrough and error table
- Bytegoblin git hooks setup guide — page-1 evidence: git's native hook lane (the concept the framework replaces)
- dev.to "Amazing pre-commit" tutorial — page-1 evidence: community framework walkthrough
Frequently Asked Questions
Is pre-commit the same as git's pre-commit hook?
No. Git's pre-commit hook is a built-in mechanism: a script in .git/hooks that Git runs before creating a commit. Pre-commit (the framework) is a tool that installs and manages those scripts for you from a shared, version-controlled config file. The framework uses Git's hook mechanism under the hood — it is the management layer, not a replacement for the concept.
What does "Failed — files were modified by this hook" mean?
It means a formatter hook (like an auto-formatter or whitespace trimmer) changed your files and stopped the commit. Nothing is broken: review the changes, stage them, and commit again. The second commit will pass because the files are already in their formatted state.
Can I use pre-commit without knowing Python?
Yes. You only need Python to install the framework itself (pip install pre-commit, or Homebrew/conda which hide Python entirely). Writing configs and using hooks requires no Python knowledge — many hooks are written in other languages, and pre-commit builds their environments for you automatically.
How do I skip a hook for one commit?
Use SKIP=hook-id git commit -m "message" to skip one named hook, or a comma-separated list for several. Reserve git commit --no-verify for genuine emergencies — it disables every hook, including the ones that block secrets and broken code.
Should I run pre-commit in CI as well as locally?
Yes — pre-commit run --all-files in CI enforces the exact same pinned hook versions the team runs locally, catching anything that slipped past with --no-verify. The official GitHub Action (pre-commit/[email protected]) is in maintenance mode, so many teams use the hosted pre-commit.ci service instead.