9  Pre-commit Hooks

In this post, we’ll walk through how to add pre-commit to your Python package to enforce good code hygiene automatically.

This post assumes you’re already using Git and are familiar with what commits are. It also requires using uv.

9.1 TLDR: What is pre-commit?

pre-commit is a framework for managing and running “hooks”, which are just scripts defined in a .pre-commit-config.yaml file that run at specific points in the Git lifecycle. The most common is the pre-commit hook, which runs before a commit is created.

The point of using pre-commit hooks is to prevent your codebase from including unwanted things, such as unformatted code, oversized files, print statements, and so on.

Here’s what makes pre-commit awesome:

  • It runs locally – unlike CI (e.g. GitHub Actions), it catches issues before they get pushed
  • It’s fast – runs only on the files you’ve changed
  • It’s customizable – tons of hooks are available, or you can write your own
  • It integrates with CI – you can run pre-commit in CI to make sure everyone follows the same rules

9.2 How it looks

Here is a pre-commit hook that:

  • checks if our code is both linted and formatted
  • if not, it will try to fix it
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.11.7
    hooks:
      - id: ruff
        types_or: [python, pyi]
        args: [--fix]
      - id: ruff-format
        types_or: [python, pyi]

This will run each time we run git commit. If our code is not perfectly linted and formatted, it will prevent the commit and lint/format it (if possible).

Once our code is fixed, we can re-run git add and git commit, and it will accept our commit, which we can then push.

9.3 How to set up

Create a .pre-commit-config.yaml file at the root of your project.

Let’s use a relatively common pre-commit setup. It configures formatting and linting with ruff, checks for large files, and removes trailing whitespace:

repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.11.7
    hooks:
      - id: ruff
        types_or: [python, pyi]
        args: [--fix]
      - id: ruff-format
        types_or: [python, pyi]

  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v5.0.0
    hooks:
      - id: trailing-whitespace
      - id: check-added-large-files
  • The repo field defines where the actual checks to run are defined
  • Each rev pin ensures you’re using a specific version of that hook for reproducibility
  • The id defines the exact check to run

Install it as a development dependency:

uv add --dev pre-commit
uv run pre-commit install

If you don’t want to add it to your development dependencies, you can simply run :

uv pip install pre-commit
uv run pre-commit install

Now try editing a Python file (add spaces at the end of the file or change the formatting of something). Then try committing it:

git add .pre-commit-config.yaml
git commit -m "Test pre-commit"

You’ll see the hooks run automatically and fix (or block) your commit if needed.

[INFO] Installing environment for https://github.com/astral-sh/ruff-pre-commit.
[INFO] Once installed, this environment will be reused.
[INFO] Running ruff-format...
[INFO] Files were modified by this hook. Please stage the changes and try again.

If pre-commit fixes files automatically, it will ask you to re-stage them and try committing again. This prevents broken or messy code from slipping into version control.

9.4 FAQ

This means the hook fixed your files. Run:

git add -A
git commit -m "message"

Some hooks (like ruff) don’t fix issues automatically. You’ll need to fix them manually based on the error messages.

Did you run uv run pre-commit install? That installs the Git hook. Without it, the hooks won’t run.