pre-commit manages git hooks across languages. pip install pre-commit. Install hooks: pre-commit install. Run all: pre-commit run --all-files. Single hook: pre-commit run ruff --all-files. Update: pre-commit autoupdate. Skip on commit: git commit --no-verify (use sparingly). Uninstall: pre-commit uninstall. Clean cache: pre-commit clean. .pre-commit-config.yaml: repos: list with repo: URL, rev: pinned tag, hooks: [{id: hookname}]. default_stages: [pre-commit]. default_language_version: {python: python3.12}. fail_fast: true — stop after first failure. Hook fields: id, name, language, entry, types, exclude, files, args, always_run, pass_filenames, stages. Built-in pre-commit/pre-commit-hooks repo: trailing-whitespace, end-of-file-fixer, check-yaml, check-json, check-toml, check-xml, check-merge-conflict, check-added-large-files, detect-private-key, no-commit-to-branch, mixed-line-ending. Custom local hook: - repo: local; hooks: [{id: pytest-unit, name: unit tests, entry: pytest tests/unit, language: system, pass_filenames: false, always_run: true}]. Stages: pre-commit (default), commit-msg, pre-push, manual. Install commit-msg hook: pre-commit install --hook-type commit-msg. pre-commit.ci: add .pre-commit-ci.yaml with autofix_prs: true, autoupdate_schedule: weekly. SKIP=ruff git commit -m "..." — skip specific hooks by ID. Environment: additional_dependencies: ["ruff==0.4.0"] — pin hook tool versions. language: python hooks run in hook’s own virtualenv. language: system uses project’s virtualenv. Claude Code generates pre-commit configs for Python, JavaScript, Go, and polyglot projects.
CLAUDE.md for pre-commit
## pre-commit Stack
- Version: pre-commit >= 3.7 | pip install pre-commit
- Install: pre-commit install (once per clone)
- Run: pre-commit run --all-files | git commit triggers automatically
- Config: .pre-commit-config.yaml with repos/rev/hooks
- Update: pre-commit autoupdate (quarterly — use --freeze for pinned hashes)
- Skip: SKIP=hookid git commit (selective) | --no-verify (all hooks, rare)
- CI: pre-commit run --all-files in pipeline + pre-commit.ci for auto PRs
pre-commit Hook Pipeline
# .pre-commit-config.yaml
# Install: pre-commit install
# Run all: pre-commit run --all-files
# Update revs: pre-commit autoupdate
default_language_version:
python: python3.12
default_stages: [pre-commit]
# Fail fast: stop after first failing hook (set false for full report)
fail_fast: false
repos:
# ── 1. Universal file hygiene ──────────────────────────────────────────────
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
# Remove trailing whitespace from all text files
- id: trailing-whitespace
args: [--markdown-linebreak-ext=md]
# Ensure every file ends with a newline
- id: end-of-file-fixer
# Validate YAML syntax
- id: check-yaml
args: [--allow-multiple-documents]
# Validate JSON syntax
- id: check-json
# Validate TOML syntax
- id: check-toml
# Detect git merge conflict markers left in files
- id: check-merge-conflict
# Block committing files > 500 KB (use Git LFS for large assets)
- id: check-added-large-files
args: [--maxkb=500]
# Scan for private keys accidentally staged
- id: detect-private-key
# Prevent direct commits to main/master
- id: no-commit-to-branch
args: [--branch, main, --branch, master]
# Normalize line endings (prefer LF on all platforms)
- id: mixed-line-ending
args: [--fix=lf]
# Verify Python files parse (catches syntax errors before CI)
- id: check-ast
# Verify __init__.py files in packages exist
- id: check-docstring-first
# ── 2. Ruff — linting + formatting (replaces flake8, isort, Black) ─────────
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.4
hooks:
# Lint and auto-fix: import sorting, pyupgrade, bugbear, etc.
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
# Format: Black-compatible whitespace and indentation
- id: ruff-format
# ── 3. mypy — static type checking ────────────────────────────────────────
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.10.0
hooks:
- id: mypy
args: [--strict, --ignore-missing-imports]
# Only run on .py files in src/ (skip tests for speed)
files: ^src/
additional_dependencies:
- types-requests
- types-PyYAML
- types-python-dateutil
# ── 4. Bandit — security linting ──────────────────────────────────────────
- repo: https://github.com/PyCQA/bandit
rev: 1.7.8
hooks:
- id: bandit
args: [-c, pyproject.toml, -ll] # HIGH severity only
files: ^src/
additional_dependencies: ["bandit[toml]"]
# ── 5. Commit message linting ──────────────────────────────────────────────
- repo: https://github.com/commitizen-tools/commitizen
rev: v3.27.0
hooks:
- id: commitizen
stages: [commit-msg]
# ── 6. Secrets scanning ───────────────────────────────────────────────────
- repo: https://github.com/Yelp/detect-secrets
rev: v1.5.0
hooks:
- id: detect-secrets
args: [--baseline, .secrets.baseline]
exclude: tests/fixtures/
# ── 7. Local hooks — project-specific checks ──────────────────────────────
- repo: local
hooks:
# Fast unit tests (no slow/integration tests)
- id: pytest-unit
name: pytest unit tests
entry: pytest tests/unit/ -x -q --tb=short
language: system
pass_filenames: false
always_run: true
stages: [pre-push] # only on push, not every commit
# Check that pyproject.toml is valid
- id: check-pyproject
name: validate pyproject.toml
entry: python -c "import tomllib; tomllib.load(open('pyproject.toml','rb'))"
language: system
files: ^pyproject\.toml$
pass_filenames: false
# Enforce no print() statements in src/ (use logger instead)
- id: no-print-statements
name: no print() in src/
entry: ruff check --select T201 src/
language: system
pass_filenames: false
files: ^src/.*\.py$
# scripts/pre_commit_helpers.py
# Utility functions for pre-commit workflow management.
from __future__ import annotations
import subprocess
import sys
from pathlib import Path
def install_hooks(hook_types: list[str] | None = None) -> None:
"""
Install pre-commit hooks for the current repo.
Installs pre-commit and pre-push hooks by default.
Run once after cloning: python scripts/pre_commit_helpers.py install
"""
cmds = [["pre-commit", "install"]]
for ht in (hook_types or ["commit-msg"]):
cmds.append(["pre-commit", "install", "--hook-type", ht])
for cmd in cmds:
subprocess.run(cmd, check=True)
print("pre-commit hooks installed.")
def run_all(fix: bool = True) -> int:
"""
Run all hooks on every file.
Used in CI and for bulk clean-up after adding new hooks.
Returns the exit code (0 = all pass, non-zero = failures).
"""
env_skip = "" # set to "mypy,bandit" to skip slow hooks in dev
cmd = ["pre-commit", "run", "--all-files"]
result = subprocess.run(cmd, check=False)
return result.returncode
def autoupdate(freeze: bool = False) -> None:
"""
Update all hook revs to the latest tagged version.
Use --freeze to pin to commit SHAs instead of tags.
Run quarterly or before a security audit.
"""
cmd = ["pre-commit", "autoupdate"]
if freeze:
cmd.append("--freeze")
subprocess.run(cmd, check=True)
def generate_secrets_baseline(output: str = ".secrets.baseline") -> None:
"""
Generate a baseline file for detect-secrets.
Run once to acknowledge existing secrets in the repo, then commit the baseline.
New secrets introduced after the baseline will fail the hook.
"""
subprocess.run(
["detect-secrets", "scan", "--baseline", output],
check=True,
)
print(f"Secrets baseline written to {output}")
print("Commit .secrets.baseline to the repo.")
# ── GitHub Actions CI integration ─────────────────────────────────────────────
GITHUB_ACTIONS_PRE_COMMIT = """
# .github/workflows/pre-commit.yml
name: pre-commit
on:
push:
branches: [main]
pull_request:
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/[email protected]
# This action caches hook environments and runs pre-commit run --all-files
# Fails the PR if any hook fails or modifies files
"""
# ── .pre-commit-ci.yaml — cloud auto-fix service ─────────────────────────────
PRE_COMMIT_CI_CONFIG = """
# .pre-commit-ci.yaml — enables pre-commit.ci to auto-fix PRs
ci:
autofix_commit_msg: "style: pre-commit auto-fixes"
autofix_prs: true
autoupdate_commit_msg: "chore: pre-commit autoupdate"
autoupdate_schedule: weekly
skip: [mypy, bandit, pytest-unit] # skip slow hooks in CI auto-fix
submodules: false
"""
# ── Developer onboarding script ───────────────────────────────────────────────
ONBOARDING_SCRIPT = """
#!/usr/bin/env bash
# scripts/setup_dev.sh — run once after cloning the repo
set -euo pipefail
echo "Setting up development environment..."
# 1. Install Python dependencies
pip install -e ".[dev]"
# 2. Install pre-commit hooks
pre-commit install
pre-commit install --hook-type commit-msg
pre-commit install --hook-type pre-push
# 3. Generate secrets baseline if not present
if [ ! -f .secrets.baseline ]; then
detect-secrets scan --baseline .secrets.baseline
echo "Secrets baseline generated — commit .secrets.baseline"
fi
# 4. Run hooks once on all files to verify setup
pre-commit run --all-files || echo "Some hooks failed — fix before committing"
echo "Done. pre-commit hooks active for all future git operations."
"""
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "install":
install_hooks()
elif len(sys.argv) > 1 and sys.argv[1] == "run":
sys.exit(run_all())
elif len(sys.argv) > 1 and sys.argv[1] == "update":
autoupdate(freeze="--freeze" in sys.argv)
else:
print("Usage: python pre_commit_helpers.py [install|run|update]")
For the husky + lint-staged alternative (JavaScript) — husky manages git hooks for Node.js projects and lint-staged runs linters only on staged files, but both are npm-only while pre-commit works across Python, Go, Rust, shell, and YAML in the same .pre-commit-config.yaml, shares a hook cache across all repos on the machine, and pre-commit autoupdate handles version bumps for every tool in one command. For the Makefile pre-commit target alternative — running make lint manually before commits is forgotten by at least one team member per sprint, while pre-commit install makes the hook fire automatically on every git commit without developer action, and pre-commit run --all-files in the GitHub Actions workflow ensures the same rules that run locally also block PRs when a developer bypasses hooks with --no-verify. The Claude Skills 360 bundle includes pre-commit skill sets covering .pre-commit-config.yaml structure, pre-commit-hooks built-ins, Ruff and ruff-format hooks, mypy and Bandit integration, local hooks for pytest and custom checks, commit-msg stage with commitizen, detect-secrets baseline workflow, pre-commit autoupdate strategy, GitHub Actions pre-commit/action integration, and pre-commit.ci for automated PR fixes. Start with the free tier to try git hook automation code generation.