GitPre-commit Framework

Pre-commit Hooks for Code Quality

Pre-commit hooks run automatically before each git commit. They catch problems — formatting violations, linting errors, trailing whitespace, secrets accidentally staged — before they ever enter the commit history. The pre-commit Python framework makes managing hooks across a team effortless: one config file, one install command, and everyone runs the same checks.

Why use pre-commit hooks?
  • Catch style issues locally in milliseconds, not minutes later in CI.

  • Eliminate "fix linting" and "fix formatting" commits from your history.

  • Enforce consistent style without relying on everyone remembering to run formatters.

  • Block secrets (API keys, passwords) from being committed.

  • The pre-commit framework handles installation, isolation, and updates automatically.

Install the pre-commit framework

Install pre-commit

Bash
# macOS
brew install pre-commit

# pip (any platform with Python)
pip install pre-commit

# With pipx (recommended for isolated install)
pipx install pre-commit

# Verify
pre-commit --version
# pre-commit 3.7.0
The .pre-commit-config.yaml file

Create .pre-commit-config.yaml in the root of your repository. This file defines which hooks to run. It is committed to version control so the whole team shares the same configuration.

.pre-commit-config.yaml — comprehensive example

YAML
repos:
  # --- General file hygiene ---
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.6.0
    hooks:
      - id: trailing-whitespace        # remove trailing spaces
      - id: end-of-file-fixer          # ensure files end with a newline
      - id: check-yaml                 # validate YAML syntax
      - id: check-json                 # validate JSON syntax
      - id: check-toml                 # validate TOML syntax
      - id: check-merge-conflict       # block conflict markers
      - id: check-added-large-files    # warn on files > 500KB
        args: ['--maxkb=500']
      - id: detect-private-key         # block private keys from being committed
      - id: no-commit-to-branch        # prevent direct commits to main
        args: ['--branch', 'main', '--branch', 'master']

  # --- JavaScript / TypeScript: Prettier ---
  - repo: https://github.com/pre-commit/mirrors-prettier
    rev: v3.1.0
    hooks:
      - id: prettier
        types_or: [javascript, jsx, ts, tsx, css, json, yaml, markdown]

  # --- JavaScript / TypeScript: ESLint ---
  - repo: https://github.com/pre-commit/mirrors-eslint
    rev: v8.57.0
    hooks:
      - id: eslint
        files: \.([jt]sx?)$
        types: [file]
        additional_dependencies:
          - eslint@8.57.0
          - '@typescript-eslint/eslint-plugin@7.0.0'
          - '@typescript-eslint/parser@7.0.0'

  # --- Python: Black formatter ---
  - repo: https://github.com/psf/black
    rev: 24.4.2
    hooks:
      - id: black
        language_version: python3

  # --- Python: Ruff linter (fast alternative to flake8) ---
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.4.1
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format

  # --- Python: mypy type checking ---
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.9.0
    hooks:
      - id: mypy
        additional_dependencies: [types-requests, types-PyYAML]

  # --- Secrets detection ---
  - repo: https://github.com/Yelp/detect-secrets
    rev: v1.4.0
    hooks:
      - id: detect-secrets
        args: ['--baseline', '.secrets.baseline']
Install hooks into your .git directory

Install and verify

Bash
# Install the hooks (run once per repo clone)
pre-commit install
# pre-commit installed at .git/hooks/pre-commit

# Now hooks run automatically on every git commit
git commit -m "test"
# [INFO] Initializing environment for ...
# Trim Trailing Whitespace...............Passed
# End of files fixer.....................Passed
# Check Yaml.............................Passed
# Prettier...............................Passed
# ESLint.................................Passed
# [main abc1234] test
First run is slow
The first time you run pre-commit, it creates isolated virtual environments for each hook. Subsequent runs are fast because the environments are cached.
Running hooks manually

Manual hook runs

Bash
# Run all hooks against all files (useful for initial setup or CI)
pre-commit run --all-files

# Run a specific hook only
pre-commit run prettier --all-files
pre-commit run eslint --all-files

# Run against specific files
pre-commit run --files src/app.ts src/utils.ts

# Update all hook versions to latest
pre-commit autoupdate
Integrating with CI

Running pre-commit in CI catches violations that slipped through (e.g. teammates who bypassed hooks with --no-verify or have not installed hooks). The CI run uses --all-files to check everything.

.github/workflows/pre-commit.yml

YAML
name: Pre-commit

on:
  pull_request:
  push:
    branches: [main]

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
Tip
The official `pre-commit/action` GitHub Action handles caching the pre-commit environments automatically. It is the easiest way to run pre-commit in CI.
Common hook configurations

Excluding files from specific hooks

YAML
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.6.0
    hooks:
      - id: check-added-large-files
        exclude: '^tests/fixtures/'   # allow large test fixtures

  - repo: https://github.com/pre-commit/mirrors-prettier
    rev: v3.1.0
    hooks:
      - id: prettier
        exclude: '^(dist|.next|coverage)/'   # skip generated files

Running a hook only on specific file types

YAML
- repo: https://github.com/psf/black
  rev: 24.4.2
  hooks:
    - id: black
      types: [python]   # only runs on .py files
Bypassing hooks in emergencies

Skip hooks when needed

Bash
# Skip all pre-commit hooks for a single commit
git commit --no-verify -m "emergency fix: skip hooks"

# Skip a specific hook
SKIP=eslint git commit -m "WIP: skip only eslint"
Warning
`git commit --no-verify` bypasses all hooks. Use it only in genuine emergencies, and follow up immediately with a commit that passes all checks. Make it a team norm to never bypass hooks on shared branches without a plan to fix the violation.
Comparison: frameworks for managing hooks

Feature

pre-commit framework

Husky (Node)

Shell scripts in .git/hooks

Language

Python (manages any language hook)

Node.js / npm

Bash

Team sharing

Yes — .pre-commit-config.yaml in repo

Yes — .husky/ in repo

No — .git/ is not committed

Isolated environments

Yes — each hook in its own venv

No — uses global install

No

Hook sources

GitHub repos, local, Docker

npm packages

Any script

Version pinning

Yes — pin rev per hook

Via npm versions

Manual

CI integration

Official GitHub Action available

Run via npm scripts

Any script runner

Best for

Polyglot projects, security-focused

Node.js-only projects

Quick one-off hooks

Setting up a new team member

Onboarding steps after cloning a repo with pre-commit

Bash
# After cloning the repo
git clone https://github.com/org/repo.git
cd repo

# Install pre-commit (if not already installed)
pip install pre-commit

# Install the hooks (this is the only per-clone setup step)
pre-commit install

# Run against all files to confirm everything passes
pre-commit run --all-files
# All checks pass — you are ready to commit