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.

MethodCommandWhen to use it
pippip install pre-commitInside a Python project's environment; pin it in requirements files
pipxpipx install pre-commitIsolated global install (works because the package is on PyPI; not the official docs' method)
Homebrewbrew install pre-commitmacOS or Linux without touching a Python environment
condaconda install conda-forge::pre-commitAnaconda/miniconda shops
Zero-dependency zipappDownload pre-commit-4.7.0.pyz from GitHub releasesCI 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:

KeyWhere it livesWhat it does
reposTop levelThe list of hook repositories to use
repoPer repo entryGit URL to clone, or local / meta for special hooks
revPer repo entryThe tag or revision to check out — pin this, never float it
hooksPer repo entryWhich hooks to run, each with an id
idPer hookThe hook's name inside its repository
files / excludePer hook or top levelRegex filters controlling which filenames the hook sees
types / types_orPer hookFile-type filters (for example only Python files); ANDed vs ORed
argsPer hookExtra arguments passed to the hook before the filenames
stagesPer hookWhich 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:

  1. Install the framework (pip install pre-commit)
  2. Generate the config (pre-commit sample-config > .pre-commit-config.yaml)
  3. Edit the config to add the hooks your team wants
  4. Activate it (pre-commit install)
  5. Stage a file and commit — watch the hooks run
  6. 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-commitHuskyLefthook
LanguagesMulti-language (Python, Node, Go, Ruby, Rust, Docker, system, more)Node/JavaScript ecosystemFast runner, JS-centric configs
Config.pre-commit-config.yaml (shareable, versioned)npm scripts + hook fileslefthook.yml
Hook isolationIsolated environments per hook languageRuns in project environmentRuns in project environment
Version pinningrev per hook repo + autoupdatenpm versioningManual / package manager
Best forPolyglot repos and teams wanting one standardJS-only projectsJS 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.

MistakeWhat actually happensThe fix
Installing the hook before writing the configCommits error out because there is nothing configuredConfig first, pre-commit install second — always
Expecting hooks to check unstaged workHooks pass on staged files while your unstaged edits hide bugsRemember the stash: stage everything the hook should see
Panicking when a formatter "fails"Failed — Files were modified by this hook looks like breakageIt fixed your files; stage them and commit again
Leaving rev floating or never updatingEither breakage at 2 a.m. or years of stale checksPin revs, run pre-commit autoupdate monthly, commit the diff
Using --no-verify as a habitThe whole safety net goes dark, including secret blockersSKIP=hook-id for one hook, one commit, documented
Never cleaning the cacheGigabytes of old hook environments accumulate silentlypre-commit clean and pre-commit gc periodically
Copying a config without reading the schemaUnknown 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

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.

Featured on Twelve Tools Launch Llama Tools Launch your MCP