---
# === IDENTITY ===
id: software/devops/pre-commit-hooks/2026
canonical_question: "Pre-commit hooks setup reference"
aliases:
  - "How to set up pre-commit hooks with .pre-commit-config.yaml"
  - "pre-commit framework configuration guide"
  - "Git pre-commit hooks for linting and formatting"
  - "pre-commit hooks for Python and JavaScript projects"
entity_type: software_reference
domain: software > devops > Pre-commit Hooks Setup
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === VERIFICATION ===
last_verified: 2026-02-28
confidence: 0.92
version: 1.0
first_published: 2026-02-28

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "pre-commit 4.0 (2024) — dropped Python 3.8, updated hook specification"
  next_review: 2026-08-27
  change_sensitivity: medium

# === CONSTRAINTS ===
constraints:
  - "pre-commit requires Python 3.9+ (v4.x) — install globally or in a dedicated venv, not your project venv"
  - ".pre-commit-config.yaml must be at the repository root — pre-commit will not find it in subdirectories"
  - "Hooks only run on staged files by default — unstaged changes are stashed and restored automatically"
  - "pre-commit install must be run once per clone — hooks are not automatically installed when someone clones your repo"
  - "Hook repos are pinned by rev (tag/SHA) — never use branch names like 'main' as they cause non-reproducible builds"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Using Husky for Node.js-only projects"
    use_instead: "Husky documentation at typicode.github.io/husky"
  - condition: "Using lefthook instead of pre-commit"
    use_instead: "Lefthook documentation at github.com/evilmartians/lefthook"

# === AGENT HINTS ===
inputs_needed:
  - key: "language_stack"
    question: "What is your primary language stack?"
    type: choice
    options: ["Python", "JavaScript/TypeScript", "Both (fullstack)", "Go", "Rust"]
  - key: "existing_tools"
    question: "Which linters/formatters do you already use?"
    type: text

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/devops/pre-commit-hooks/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-02-28)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "software/devops/eslint-prettier-config/2026"
      label: "ESLint + Prettier Configuration Reference"
    - id: "software/devops/pytest-configuration/2026"
      label: "pytest Testing Configuration Reference"
  solves: []
  alternative_to: []
  often_confused_with: []

# === SOURCES ===
sources:
  - id: src1
    title: "pre-commit — Official Documentation"
    author: pre-commit
    url: https://pre-commit.com/
    type: official_docs
    published: 2024-01-01
    reliability: authoritative
  - id: src2
    title: "pre-commit hooks — Supported Hooks Directory"
    author: pre-commit
    url: https://pre-commit.com/hooks.html
    type: official_docs
    published: 2024-01-01
    reliability: authoritative
  - id: src3
    title: "pre-commit/pre-commit-hooks — GitHub"
    author: pre-commit
    url: https://github.com/pre-commit/pre-commit-hooks
    type: official_docs
    published: 2024-01-01
    reliability: high
  - id: src4
    title: "ruff-pre-commit — GitHub"
    author: Astral
    url: https://github.com/astral-sh/ruff-pre-commit
    type: official_docs
    published: 2024-06-01
    reliability: high
  - id: src5
    title: "Pre-commit Hook Creation Guide — Stefanie Molin"
    author: Stefanie Molin
    url: https://stefaniemolin.com/articles/devx/pre-commit/hook-creation-guide/
    type: technical_blog
    published: 2024-09-01
    reliability: moderate_high
  - id: src6
    title: "How to Set Up Pre-Commit Hooks — Stefanie Molin"
    author: Stefanie Molin
    url: https://stefaniemolin.com/articles/devx/pre-commit/setup-guide/
    type: technical_blog
    published: 2024-09-01
    reliability: moderate_high
  - id: src7
    title: "Effortless Code Quality: The Ultimate Pre-Commit Hooks Guide for 2025"
    author: Gatlen Culp
    url: https://gatlenculp.medium.com/effortless-code-quality-the-ultimate-pre-commit-hooks-guide-for-2025-57ca501d9835
    type: technical_blog
    published: 2025-01-15
    reliability: moderate_high
---

# Pre-commit Hooks Setup Reference

## TL;DR

- **Bottom line**: Use the `pre-commit` framework with `.pre-commit-config.yaml` to automatically run linters, formatters, and checks before every git commit; pin hook versions by tag for reproducibility.
- **Key tool/command**: `pre-commit install && pre-commit run --all-files`
- **Watch out for**: Forgetting to run `pre-commit install` after cloning — hooks are git-local and not propagated by `git clone`.
- **Works with**: Any git repository, Python 3.9+, hooks for every major language (Python, JS/TS, Go, Rust, YAML, Markdown).

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- pre-commit requires Python 3.9+ (v4.x) — install globally or in a dedicated venv, not your project venv
- `.pre-commit-config.yaml` must be at the repository root — pre-commit will not find it in subdirectories
- Hooks only run on staged files by default — unstaged changes are stashed and restored automatically
- `pre-commit install` must be run once per clone — hooks are not automatically installed when someone clones your repo
- Hook repos are pinned by `rev` (tag/SHA) — never use branch names like `main` as they cause non-reproducible builds

## Quick Reference

| Command | Purpose |
|---|---|
| `pre-commit install` | Install git hooks (run once per clone) |
| `pre-commit run --all-files` | Run all hooks on entire repo |
| `pre-commit run <hook-id>` | Run a single hook |
| `pre-commit autoupdate` | Update all hook versions to latest tags |
| `pre-commit run --files file1.py file2.py` | Run hooks on specific files |
| `SKIP=hook-id git commit` | Skip specific hook for one commit |
| `git commit --no-verify` | Skip ALL hooks (emergency only) |
| `pre-commit clean` | Clear cached hook environments |
| `pre-commit gc` | Garbage collect unused hook envs |
| `pre-commit install --hook-type pre-push` | Install hooks for pre-push stage |
| `pre-commit validate-config` | Validate .pre-commit-config.yaml syntax |
| `pre-commit try-repo <repo-url>` | Test a hook repo without installing |

## Decision Tree

```
START
├── Python project?
│   ├── YES → Add ruff (lint+format), mypy (types), pre-commit-hooks (basics)
│   └── NO ↓
├── JavaScript/TypeScript project?
│   ├── YES → Add eslint, prettier via mirrors-eslint/mirrors-prettier
│   └── NO ↓
├── Full-stack (Python + JS)?
│   ├── YES → Combine: ruff + mypy + eslint + prettier + pre-commit-hooks
│   └── NO ↓
├── Go project?
│   ├── YES → Add golangci-lint, gofmt
│   └── NO ↓
├── Rust project?
│   ├── YES → Add cargo fmt, clippy
│   └── NO ↓
├── Need secrets detection?
│   ├── YES → Add detect-secrets or gitleaks
│   └── NO ↓
└── DEFAULT → pre-commit-hooks (trailing whitespace, EOF, YAML/JSON checks)
```

## Step-by-Step Guide

### 1. Install pre-commit

Install the pre-commit framework. [src1]

```bash
# Option A: pip (most common)
pip install pre-commit

# Option B: pipx (isolated install, recommended)
pipx install pre-commit

# Option C: Homebrew (macOS/Linux)
brew install pre-commit
```

**Verify**: `pre-commit --version` → `pre-commit 4.x.x`

### 2. Create .pre-commit-config.yaml

Create the configuration file at the repository root. [src1]

```yaml
# .pre-commit-config.yaml
repos:
  # Basic file checks
  - 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-json
      - id: check-added-large-files
        args: ['--maxkb=500']
      - id: check-merge-conflict
      - id: detect-private-key
```

**Verify**: `pre-commit validate-config` → no errors

### 3. Install git hooks

Register pre-commit to run on every `git commit`. [src1]

```bash
pre-commit install
```

**Verify**: `cat .git/hooks/pre-commit` → shows pre-commit hook script

### 4. Run on all existing files

Verify the entire codebase passes. [src1]

```bash
pre-commit run --all-files
```

**Verify**: All hooks pass or show actionable fixes

### 5. Add language-specific hooks

Add linters and formatters for your stack. [src4]

```yaml
# .pre-commit-config.yaml — Python project additions
  # Ruff — fast Python linter + formatter (replaces black, isort, flake8)
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.9.6
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format

  # MyPy — static type checking
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.14.1
    hooks:
      - id: mypy
        additional_dependencies: [types-requests]
```

**Verify**: `pre-commit run ruff --all-files` → linting passes

### 6. Keep hooks updated

Regularly update hook versions to get latest fixes. [src1]

```bash
pre-commit autoupdate
```

**Verify**: `git diff .pre-commit-config.yaml` → shows version bumps

## Code Examples

### Python project: Ruff + MyPy + basic checks

> Full script: [python-project-ruff-mypy-basic-checks.yml](scripts/python-project-ruff-mypy-basic-checks.yml) (35 lines)

```yaml
# .pre-commit-config.yaml — Python data science / web project
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v5.0.0
    hooks:
# ... (see full script)
```

### JavaScript/TypeScript project: ESLint + Prettier

> Full script: [javascript-typescript-project-eslint-prettier.yml](scripts/javascript-typescript-project-eslint-prettier.yml) (26 lines)

```yaml
# .pre-commit-config.yaml — Node.js / React / Vue project
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v5.0.0
    hooks:
# ... (see full script)
```

### Full-stack project: Python + JS + security

> Full script: [full-stack-project-python-js-security.yml](scripts/full-stack-project-python-js-security.yml) (43 lines)

```yaml
# .pre-commit-config.yaml — monorepo with Python backend + React frontend
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v5.0.0
    hooks:
# ... (see full script)
```

## Anti-Patterns

### Wrong: Using branch names instead of tags for rev

```yaml
# ❌ BAD — branch names change over time, non-reproducible
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: main  # Will fetch different code each time!
    hooks:
      - id: ruff
```

### Correct: Pin to specific version tags

```yaml
# ✅ GOOD — pinned to immutable version tag
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.9.6  # Pinned, reproducible
    hooks:
      - id: ruff
```

### Wrong: Running heavy checks in pre-commit (type checking full project)

```yaml
# ❌ BAD — runs mypy on entire project for every commit (30+ seconds)
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.14.1
    hooks:
      - id: mypy
        args: [--strict]
        pass_filenames: false  # Checks ALL files, not just staged
```

### Correct: Only check changed files or use CI for full checks

```yaml
# ✅ GOOD — mypy only checks staged files (fast)
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.14.1
    hooks:
      - id: mypy
        args: [--ignore-missing-imports]
        # Default: pass_filenames: true (only staged files)
```

### Wrong: Not documenting hook installation in README

```yaml
# ❌ BAD — new contributors don't know hooks exist
# .pre-commit-config.yaml exists but nobody runs `pre-commit install`
```

### Correct: Add to setup instructions and CI

```bash
# ✅ GOOD — in Makefile or project setup script
# Makefile
setup:
	pip install pre-commit
	pre-commit install
	pre-commit install --hook-type commit-msg

# In CI (.github/workflows/lint.yml):
# - run: pre-commit run --all-files
```

## Common Pitfalls

- **Hooks not running**: Forgot `pre-commit install` after clone. Fix: add to project setup script or `Makefile`. [src1]
- **Slow commits**: Heavy hooks (mypy full project, large ESLint scope). Fix: use `pass_filenames: true` (default) and move full checks to CI. [src6]
- **Hook environment stale**: Cached environment from old version. Fix: `pre-commit clean && pre-commit install`. [src1]
- **additional_dependencies missing**: mypy needs type stubs. Fix: list all type stub packages in `additional_dependencies`. [src2]
- **Wrong file matching**: Hook runs on files it should not. Fix: use `files`, `exclude`, or `types_or` to scope hooks correctly. [src1]
- **CI and local disagree**: Different hook versions. Fix: run `pre-commit autoupdate` and commit the changes; CI should use `pre-commit run --all-files`. [src7]

## Diagnostic Commands

```bash
# Check pre-commit version
pre-commit --version

# Validate config file syntax
pre-commit validate-config

# Run all hooks on all files
pre-commit run --all-files

# Run a specific hook
pre-commit run ruff --all-files

# See which hooks would run
pre-commit run --all-files --verbose

# Update all hooks to latest versions
pre-commit autoupdate

# Clear cached hook environments
pre-commit clean

# Test a hook repo before adding
pre-commit try-repo https://github.com/astral-sh/ruff-pre-commit --all-files

# Show installed hook types
ls -la .git/hooks/
```

## Version History & Compatibility

| Version | Status | Breaking Changes | Migration Notes |
|---|---|---|---|
| pre-commit 4.x | Current | Python 3.8 dropped, spec updates | Ensure Python 3.9+ for host install |
| pre-commit 3.x | Maintenance | — | Last version supporting Python 3.8 |
| pre-commit 2.x | EOL | — | Upgrade to 3+ for security patches |
| Ruff | Current | Replaces black, isort, flake8, pycodestyle | Use ruff + ruff-format instead of separate tools |
| mirrors-eslint | Current | Tracks ESLint releases | Pin `additional_dependencies` versions |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Any git repository with 2+ contributors | Solo prototype / quick hack | Manual checks |
| Need consistent code quality on every commit | CI-only checks are sufficient | GitHub Actions linting |
| Multi-language project (Python + JS + YAML) | Node.js-only project (prefer JS ecosystem) | Husky + lint-staged |
| Want language-agnostic hook management | Using Nix-based development | Nix pre-commit-hooks |

## Important Caveats

- `pre-commit install` only configures the current clone — each developer must run it, and CI must run `pre-commit run --all-files`
- `--no-verify` on `git commit` skips ALL hooks — use `SKIP=hook-id` to skip specific hooks instead
- Hooks run on staged content, not working directory — if a hook modifies files (e.g., formatting), the changes are unstaged and must be re-added
- `additional_dependencies` for mirrors-eslint/mirrors-mypy must be kept in sync with your project's package versions
- Pre-commit manages its own virtual environments per hook — this can use significant disk space over time; run `pre-commit gc` periodically

## Related Units

- [ESLint + Prettier Configuration Reference](/software/devops/eslint-prettier-config/2026)
- [pytest Testing Configuration Reference](/software/devops/pytest-configuration/2026)
