Neural Architecture Hub
Python

Pre-commit

Pre-commit hooks, configuration, workflows, and CI reference.

Pre-commit Developer Guide

pre-commit is a framework for running automated checks before code enters your Git history. It can format files, catch simple mistakes, validate configuration, and run project tools such as Ruff, Black, or mypy.

Getting Started

What a Hook Does

A Git hook is a command that runs at a Git lifecycle event. The most common event is pre-commit, which runs after git commit is requested but before the commit is created.

edit files -> git add -> git commit -> hooks run -> commit is created
                                     -> failure: fix and stage changes again

Use hooks to catch repeatable issues early. Keep expensive integration tests in CI unless the team intentionally wants them during local commits.

Installation

Install pre-commit in an isolated tool environment where possible:

pipx install pre-commit              # Recommended for a globally available CLI
uv tool install pre-commit           # Install with uv
python -m pip install pre-commit     # Install into the active Python environment

pre-commit --version                 # Verify installation

Create a Configuration File

Hooks are declared in .pre-commit-config.yaml at the root of the repository. Start with a sample configuration or create one manually:

pre-commit sample-config > .pre-commit-config.yaml

A useful starter configuration:

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-added-large-files
      - id: check-merge-conflict
      - id: debug-statements

Each repository is pinned to a rev so every developer and CI run uses the same hook version.

Install the Git Hook

After the configuration file exists, install it into the local Git repository:

pre-commit install                   # Install the pre-commit hook
pre-commit install --install-hooks   # Install hook script and environments now
git add .pre-commit-config.yaml
git commit -m "Configure pre-commit hooks"

The first hook run may take longer because pre-commit builds isolated environments for the configured tools.

Everyday Workflow

Run Hooks During a Commit

Once installed, hooks run automatically on staged files:

git add app.py
git commit -m "Add application logic"

If a formatter modifies a file or a check fails:

git diff                              # Review fixes made by hooks
git add .                             # Stage the corrected files
git commit -m "Add application logic" # Try the commit again

Run Hooks Manually

Run checks before committing or across existing files:

pre-commit run                        # Run hooks on staged files
pre-commit run --all-files            # Run every hook on every tracked file
pre-commit run trailing-whitespace    # Run one hook
pre-commit run --files app.py test_app.py
pre-commit run --show-diff-on-failure

--all-files is useful when first adding hooks to an existing repository because older files are not normally checked until they are changed.

See More Detail

Use verbose output and configuration validation when diagnosing failures:

pre-commit run --all-files --verbose
pre-commit validate-config
pre-commit validate-manifest          # For repositories that publish hooks
pre-commit clean                      # Remove cached hook environments

Common Starter Hooks

The pre-commit-hooks project contains general checks that work in many codebases:

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-ast                 # Validate Python syntax
      - id: check-json
      - id: check-toml
      - id: check-yaml
      - id: check-merge-conflict
      - id: check-added-large-files
        args: ["--maxkb=1000"]
      - id: detect-private-key

Choose hooks that enforce project rules without surprising contributors. For example, end-of-file-fixer and trailing-whitespace change files automatically, while check-yaml only reports invalid YAML.

Python Project Setup

Ruff Formatting and Linting

Ruff can replace several fast Python formatting and linting steps:

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

Put ruff-check --fix before ruff-format so automatically fixed imports or code are formatted afterward. Update pinned revisions regularly rather than assuming sample revisions are the newest releases.

Black and isort

Projects already using Black and isort can run their established tools:

repos:
  - repo: https://github.com/pycqa/isort
    rev: 5.13.2
    hooks:
      - id: isort

  - repo: https://github.com/psf/black-pre-commit-mirror
    rev: 24.10.0
    hooks:
      - id: black
        language_version: python3

Configure these tools in pyproject.toml so local hooks, editors, and CI use one shared rule set:

[tool.black]
line-length = 88

[tool.isort]
profile = "black"

Type Checking

Type checking often needs the project dependencies or type stubs:

repos:
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.15.0
    hooks:
      - id: mypy
        additional_dependencies:
          - types-requests

For applications with complex imports or plugins, a local hook using the project's managed environment may be easier to keep consistent than a separate hook environment.

Configuration Reference

Repository and Hook Fields

repos:
  - repo: https://github.com/example/tool
    rev: v1.2.3                       # Pinned tag or commit
    hooks:
      - id: tool-check                # Hook supplied by the repository
        name: run tool check          # Optional display name
        args: [--fix]                 # Extra CLI arguments
        files: '\.py$'                # Include matching paths
        exclude: '^generated/'        # Exclude matching paths
        stages: [pre-commit]          # Git stage at which it runs
        additional_dependencies: []   # Packages added to its environment

Common top-level defaults:

default_install_hook_types: [pre-commit, pre-push]
default_stages: [pre-commit]
fail_fast: false
minimum_pre_commit_version: "3.0.0"

Include and Exclude Files

Filter hooks with regular expressions:

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v5.0.0
    hooks:
      - id: check-yaml
        files: ^(config|deploy)/.*\.ya?ml$
        exclude: ^config/generated/

Use a repository-wide exclusion for generated or vendored content:

exclude: |
  (?x)^(
    migrations/|
    vendor/|
    dist/|
    docs/generated/
  )

Pass Arguments to Hooks

Hook-specific command arguments are supplied as YAML arrays:

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v5.0.0
    hooks:
      - id: check-added-large-files
        args: [--maxkb=500]
      - id: trailing-whitespace
        args: [--markdown-linebreak-ext=md]

Hook Stages

Run on Commit or Push

Install additional Git hook types and assign expensive checks only where they belong:

default_install_hook_types: [pre-commit, pre-push]

repos:
  - repo: local
    hooks:
      - id: unit-tests
        name: run unit tests before push
        entry: python -m pytest
        language: system
        pass_filenames: false
        stages: [pre-push]
pre-commit install --hook-type pre-commit
pre-commit install --hook-type pre-push
pre-commit run --hook-stage pre-push --all-files

Fast formatting and validation checks suit pre-commit. Longer tests may suit pre-push or CI.

Run a Manual Hook

Create hooks that are available on demand but do not slow normal commits:

repos:
  - repo: local
    hooks:
      - id: full-test-suite
        name: run full test suite
        entry: python -m pytest
        language: system
        pass_filenames: false
        stages: [manual]
pre-commit run --hook-stage manual full-test-suite --all-files

Local Hooks

Run Project Commands

Use repo: local for commands defined by the current project:

repos:
  - repo: local
    hooks:
      - id: pytest
        name: pytest
        entry: python -m pytest
        language: system
        pass_filenames: false
        types: [python]
      - id: compile-python
        name: compile changed Python files
        entry: python -m py_compile
        language: system
        types: [python]

language: system uses tools from the active environment. This is convenient, but each contributor and CI job must install those dependencies first.

Control Filename Handling

By default, pre-commit passes matching changed filenames to a hook. Disable that behavior for commands that discover files themselves:

repos:
  - repo: local
    hooks:
      - id: django-check
        name: Django system checks
        entry: python manage.py check
        language: system
        pass_filenames: false
        always_run: true

Keeping Hooks Updated

Pinned hook versions make builds repeatable. Upgrade them intentionally:

pre-commit autoupdate                 # Update all hook revisions
pre-commit autoupdate --repo https://github.com/pre-commit/pre-commit-hooks
pre-commit run --all-files            # Test updated hooks
git diff .pre-commit-config.yaml      # Review version changes

Commit hook upgrades separately when possible so formatting changes and tool-version changes are easy to review.

Continuous Integration

Run in CI

CI should run hooks on all tracked files so contributors who did not install local hooks still receive the same checks:

pre-commit run --all-files --show-diff-on-failure

Example GitHub Actions workflow:

name: pre-commit

on:
  pull_request:
  push:

jobs:
  pre-commit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - uses: pre-commit/action@v3.0.1

CI normally reports hook fixes rather than committing changes back to a pull request. Contributors can run the hooks locally, stage fixes, and push again.

Skipping and Bypassing Checks

Skip a Particular Hook

Temporarily skip a known hook only when there is a clear reason:

SKIP=mypy git commit -m "Work in progress"       # macOS/Linux
$env:SKIP="mypy"; git commit -m "Work in progress" # PowerShell
Remove-Item Env:SKIP                              # Clear in PowerShell

Skip All Commit Hooks

Git provides an escape hatch:

git commit --no-verify -m "Emergency commit"

Use bypasses sparingly. The same hooks should run in CI, where skipped problems can still block merging.

Troubleshooting

Hooks Modified Files

A modifying hook fails the current commit after it makes fixes. Review and stage the fixes, then commit again:

pre-commit run --all-files
git diff
git add .
git commit -m "Apply formatting fixes"

Hook Environment Problems

Clear cached environments and rebuild them:

pre-commit clean
pre-commit install --install-hooks
pre-commit run --all-files

Hook Does Not Run

Check that you are in a Git repository, the hook is installed, and its file filters match your staged changes:

git rev-parse --show-toplevel
pre-commit install
pre-commit run --all-files --verbose

A New Hook Changes Many Files

Introduce formatting changes in a dedicated commit before feature work:

pre-commit run --all-files
git add .
git commit -m "Apply pre-commit formatting"

This keeps later reviews focused on behavior changes rather than baseline cleanup.

Team Workflow

A practical adoption sequence for a Python repository:

pipx install pre-commit
pre-commit sample-config > .pre-commit-config.yaml
# Edit the configuration to select project hooks
pre-commit install
pre-commit run --all-files
git add .pre-commit-config.yaml
git add <files-updated-by-hooks>
git commit -m "Add pre-commit checks"

For a mature team:

  1. Begin with fast whitespace, syntax, and configuration checks.
  2. Add the formatter and linter already used by the project.
  3. Run all hooks in CI.
  4. Add slower hooks only when their feedback is worth the local delay.
  5. Update pinned hook revisions regularly and review resulting fixes.

Quick Reference

pre-commit install                         # Enable hooks in this clone
pre-commit install --hook-type pre-push    # Enable push-stage hooks
pre-commit run                             # Run against staged files
pre-commit run --all-files                 # Run against all tracked files
pre-commit run <hook-id> --all-files       # Run one hook everywhere
pre-commit run --hook-stage manual <id> --all-files
pre-commit autoupdate                      # Upgrade pinned hook revisions
pre-commit validate-config                 # Validate YAML configuration
pre-commit clean                           # Rebuild cached hook environments
git commit --no-verify                     # Bypass hooks temporarily

On this page