Introduction
pre-commit solves the "every developer has a slightly different setup" problem. You commit a .pre-commit-config.yaml to the repo, each developer runs pre-commit install once, and from then on every commit runs the same linters and formatters — in isolated environments, automatically installed per hook.
With over 12,000 GitHub stars, pre-commit is the standard Git hook manager across Python, JavaScript, Go, Rust, and shell projects.
What pre-commit Does
pre-commit installs itself as .git/hooks/pre-commit. On every commit it reads your config, creates virtualenvs / node_modules / go modules for each hook (cached after first run), and executes them against the staged files. Any failure blocks the commit.
Architecture Overview
git commit
|
[pre-commit hook]
|
[Read .pre-commit-config.yaml]
|
For each repo:
clone at `rev`
create isolated env (venv / npm / go / docker)
run hook ids against staged files
|
Any failure -> commit aborted
|
All pass -> commit proceedsSelf-Hosting & Configuration
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: check-added-large-files
- id: check-merge-conflict
- id: check-json
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.9
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.1.0
hooks:
- id: prettier
types_or: [javascript, ts, tsx, json, yaml, markdown]
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
- repo: local
hooks:
- id: pytest-fast
name: pytest (fast)
entry: pytest -q -x tests/unit
language: system
pass_filenames: false
stages: [pre-push]pre-commit autoupdate # bump pinned versions
pre-commit run ruff --all-files
SKIP=ruff git commit -m "wip"Key Features
- Multi-language hooks — Python, Node, Go, Rust, Ruby, Docker, system
- Isolated environments — no global pollution, cached for speed
- Run only on changed files — fast feedback for big repos
- Version pinning — hook versions tracked in config;
autoupdateto bump - CI integration —
pre-commit run --all-filesin GitHub Actions - Stages — pre-commit, pre-push, commit-msg, pre-merge-commit
- pre-commit.ci — optional SaaS that auto-fixes PRs and updates hooks
- Skipping —
SKIP=hook_idenv var for emergency commits
Comparison with Similar Tools
| Feature | pre-commit | Husky | Lefthook | Overcommit | lint-staged |
|---|---|---|---|---|---|
| Languages | Any | Node-focused | Any | Ruby-focused | Node |
| Config format | YAML | package.json/shell | YAML | YAML | package.json |
| Isolated envs | Yes | No | No | No | No |
| Hook catalog | Huge | Medium | Small | Medium | N/A |
| Best For | Polyglot repos | Node projects | Fast hooks | Ruby projects | JS file-based |
FAQ
Q: Does pre-commit require Python? A: Yes, pre-commit itself is Python. But it can run hooks in any language (Node, Go, etc.) via its env-management.
Q: Will pre-commit slow down commits? A: First install is slow (env creation), subsequent commits are fast — only staged files are linted, envs are cached.
Q: What if a dev forgets to install the hook?
A: Run pre-commit run --all-files in CI as a required check — even if local hooks are skipped, the PR still fails.
Q: Can hooks auto-fix and re-commit? A: Hooks can modify files (like ruff-format). pre-commit then aborts the commit so you can review + re-stage the fixes.
Sources
- GitHub: https://github.com/pre-commit/pre-commit
- Docs: https://pre-commit.com
- License: MIT