# How to set up pre-commit hooks for a Python project

{{< callout type="warning" >}}
This guide assumes you have a Python project managed with [uv](https://pydevtools.com/handbook/reference/uv.md). If you haven't created a project yet, see the [project creation tutorial](https://pydevtools.com/handbook/tutorial/create-your-first-python-project.md).
{{< /callout >}}

[pre-commit](https://pre-commit.com/) is a framework that manages Git hooks. It runs tools like linters and formatters automatically before each commit, catching problems before they reach version control. This guide walks through installing pre-commit, configuring hooks for a Python project, and integrating it with [Ruff](https://pydevtools.com/handbook/reference/ruff.md) and [mypy](https://pydevtools.com/handbook/reference/mypy.md).

> [!TIP]
> [prek](https://pydevtools.com/handbook/reference/prek.md) is a faster drop-in replacement for pre-commit that reads the same configuration files. See the [prek version of this guide](https://pydevtools.com/handbook/how-to/how-to-set-up-prek-hooks-for-a-python-project.md) for setup instructions.

> [!TIP]
> Using Claude Code? The [project setup tutorial](https://pydevtools.com/handbook/tutorial/set-up-a-python-project-for-claude-code.md) covers pre-commit alongside CLAUDE.md, hooks, and skills as a complete stack.

## Installing pre-commit

The easiest way to run pre-commit is through [uvx](https://pydevtools.com/handbook/explanation/when-to-use-uv-run-vs-uvx.md), which runs it without adding it as a project dependency:

```console
$ uvx pre-commit install
```

This installs a Git hook script in `.git/hooks/pre-commit` that runs automatically on `git commit`.

## Creating the configuration file

pre-commit reads its configuration from a `.pre-commit-config.yaml` file in the project root. Here is a starting configuration that uses Ruff for linting and formatting:

```yaml
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.15.16
    hooks:
      - id: ruff-check
        args: [--fix]
      - id: ruff-format
```

The `ruff-check` hook runs the linter with auto-fix enabled. The `ruff-format` hook runs the formatter. They execute in order, so linting fixes are applied before formatting.

> [!TIP]
> Run `uvx pre-commit autoupdate` periodically to update hook versions to the latest releases.

## Adding more hooks

### Trailing whitespace and file endings

The `pre-commit-hooks` repository provides several lightweight checks:

```yaml
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
```

These catch common issues: trailing whitespace, missing newlines at end of file, invalid YAML syntax, and accidentally committed large files.

### Type checking with ty

[ty](https://pydevtools.com/handbook/reference/ty.md) does not have an official pre-commit hook repository yet ([tracking issue](https://github.com/astral-sh/ty/issues/269)). Use a local hook that runs ty through `uvx`:

```yaml
  - repo: local
    hooks:
      - id: ty
        name: ty
        entry: uvx ty check
        language: system
        types: [python]
        pass_filenames: false
```

Setting `pass_filenames: false` lets ty discover files itself, which respects any `exclude` patterns in your `pyproject.toml` ty configuration.

### Type checking with mypy

To run [mypy](https://pydevtools.com/handbook/reference/mypy.md) as a pre-commit hook:

```yaml
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v2.1.0
    hooks:
      - id: mypy
        additional_dependencies: []
```

> [!NOTE]
> mypy 2.0 changed several defaults that may surface new errors in code that passed under 1.x. See the [mypy reference page](https://pydevtools.com/handbook/reference/mypy.md) for details. mypy 2.0 also requires Python 3.10 or newer as both a runtime and a target.

Add any type stub packages your project needs to `additional_dependencies`. For example, if your project uses `requests`:

```yaml
        additional_dependencies: [types-requests]
```

## A complete configuration

Combining the sections above into a single `.pre-commit-config.yaml`:

```yaml
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

  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.15.16
    hooks:
      - id: ruff-check
        args: [--fix]
      - id: ruff-format
```

## Running hooks manually

To run all hooks against every file in the repository (not just staged files):

```console
$ uvx pre-commit run --all-files
```

To run a specific hook:

```console
$ uvx pre-commit run ruff-check --all-files
```

## Skipping hooks temporarily

To bypass hooks for a single commit:

```console
$ git commit --no-verify -m "WIP: work in progress"
```

Use this sparingly. Skipping hooks defeats the purpose of having them. AI coding agents reach for `--no-verify` more readily than humans do; see [how to stop AI agents from bypassing pre-commit hooks](https://pydevtools.com/handbook/how-to/how-to-stop-ai-agents-from-bypassing-pre-commit-hooks.md) for the defensive setup.

## Using pre-commit in CI

pre-commit hooks run locally, but contributors might skip them. Running hooks in CI ensures every change is checked. Add this to a GitHub Actions workflow:

```yaml
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v8.1.0
- run: uvx pre-commit run --all-files
```

## Troubleshooting

If hooks are not running on commit, verify the hook is installed:

```console
$ uvx pre-commit install
```

If a hook fails because it cannot find dependencies, check that `additional_dependencies` is set correctly for hooks like mypy that need access to your project's packages.

To clear the pre-commit cache (useful after updating Python or hook versions):

```console
$ uvx pre-commit clean
```

## Learn More

- [How to set up prek hooks for a Python project](https://pydevtools.com/handbook/how-to/how-to-set-up-prek-hooks-for-a-python-project.md)
- [How to try the ty type checker](https://pydevtools.com/handbook/how-to/how-to-try-the-ty-type-checker.md)
- [How to configure recommended Ruff defaults](https://pydevtools.com/handbook/how-to/how-to-configure-recommended-ruff-defaults.md)
- [How to sort Python imports with Ruff](https://pydevtools.com/handbook/how-to/how-to-sort-python-imports-with-ruff.md)
- [Ruff: A Complete Guide](https://pydevtools.com/handbook/explanation/ruff-complete-guide.md)
- [How to configure mypy strict mode](https://pydevtools.com/handbook/how-to/how-to-configure-mypy-strict-mode.md)
