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 againUse 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 installationCreate 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.yamlA 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-statementsEach 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 againRun 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 environmentsCommon 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-keyChoose 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-formatPut 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: python3Configure 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-requestsFor 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 environmentCommon 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-filesFast 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-filesLocal 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: trueKeeping 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 changesCommit 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-failureExample 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.1CI 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 PowerShellSkip 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-filesHook 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 --verboseA 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:
- Begin with fast whitespace, syntax, and configuration checks.
- Add the formatter and linter already used by the project.
- Run all hooks in CI.
- Add slower hooks only when their feedback is worth the local delay.
- 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