Pre-commit Developer Guide
Section titled “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
Section titled “Getting Started”What a Hook Does
Section titled “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
Section titled “Installation”Install pre-commit in an isolated tool environment where possible:
pipx install pre-commit # Recommended for a globally available CLIuv tool install pre-commit # Install with uvpython -m pip install pre-commit # Install into the active Python environment
pre-commit --version # Verify installationCreate a Configuration File
Section titled “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.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
Section titled “Install the Git Hook”After the configuration file exists, install it into the local Git repository:
pre-commit install # Install the pre-commit hookpre-commit install --install-hooks # Install hook script and environments nowgit add .pre-commit-config.yamlgit 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
Section titled “Everyday Workflow”Run Hooks During a Commit
Section titled “Run Hooks During a Commit”Once installed, hooks run automatically on staged files:
git add app.pygit commit -m "Add application logic"If a formatter modifies a file or a check fails:
git diff # Review fixes made by hooksgit add . # Stage the corrected filesgit commit -m "Add application logic" # Try the commit againRun Hooks Manually
Section titled “Run Hooks Manually”Run checks before committing or across existing files:
pre-commit run # Run hooks on staged filespre-commit run --all-files # Run every hook on every tracked filepre-commit run trailing-whitespace # Run one hookpre-commit run --files app.py test_app.pypre-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
Section titled “See More Detail”Use verbose output and configuration validation when diagnosing failures:
pre-commit run --all-files --verbosepre-commit validate-configpre-commit validate-manifest # For repositories that publish hookspre-commit clean # Remove cached hook environmentsCommon Starter Hooks
Section titled “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-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
Section titled “Python Project Setup”Ruff Formatting and Linting
Section titled “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
Section titled “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
Section titled “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
Section titled “Configuration Reference”Repository and Hook Fields
Section titled “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: falseminimum_pre_commit_version: "3.0.0"Include and Exclude Files
Section titled “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
Section titled “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
Section titled “Hook Stages”Run on Commit or Push
Section titled “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-commitpre-commit install --hook-type pre-pushpre-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
Section titled “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
Section titled “Local Hooks”Run Project Commands
Section titled “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
Section titled “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
Section titled “Keeping Hooks Updated”Pinned hook versions make builds repeatable. Upgrade them intentionally:
pre-commit autoupdate # Update all hook revisionspre-commit autoupdate --repo https://github.com/pre-commit/pre-commit-hookspre-commit run --all-files # Test updated hooksgit 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
Section titled “Continuous Integration”Run in CI
Section titled “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
Section titled “Skipping and Bypassing Checks”Skip a Particular Hook
Section titled “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" # PowerShellRemove-Item Env:SKIP # Clear in PowerShellSkip All Commit Hooks
Section titled “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
Section titled “Troubleshooting”Hooks Modified Files
Section titled “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-filesgit diffgit add .git commit -m "Apply formatting fixes"Hook Environment Problems
Section titled “Hook Environment Problems”Clear cached environments and rebuild them:
pre-commit cleanpre-commit install --install-hookspre-commit run --all-filesHook Does Not Run
Section titled “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-toplevelpre-commit installpre-commit run --all-files --verboseA New Hook Changes Many Files
Section titled “A New Hook Changes Many Files”Introduce formatting changes in a dedicated commit before feature work:
pre-commit run --all-filesgit add .git commit -m "Apply pre-commit formatting"This keeps later reviews focused on behavior changes rather than baseline cleanup.
Team Workflow
Section titled “Team Workflow”A practical adoption sequence for a Python repository:
pipx install pre-commitpre-commit sample-config > .pre-commit-config.yaml# Edit the configuration to select project hookspre-commit installpre-commit run --all-filesgit add .pre-commit-config.yamlgit 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
Section titled “Quick Reference”pre-commit install # Enable hooks in this clonepre-commit install --hook-type pre-push # Enable push-stage hookspre-commit run # Run against staged filespre-commit run --all-files # Run against all tracked filespre-commit run <hook-id> --all-files # Run one hook everywherepre-commit run --hook-stage manual <id> --all-filespre-commit autoupdate # Upgrade pinned hook revisionspre-commit validate-config # Validate YAML configurationpre-commit clean # Rebuild cached hook environmentsgit commit --no-verify # Bypass hooks temporarily