pre-commit review
pre-commit 4.6.2 reads `.pre-commit-config.yaml`, installs pinned hook repositories into isolated language environments, selects files for a Git hook stage, and runs each hook before Git continues. It supports stages such as `pre-commit`, `commit-msg`, `pre-push`, and `manual`, plus local commands when no published hook exists. The framework supplies orchestration, caching, filtering, and environment setup; linters and formatters come from the configured repositories. Our `import pre_commit` check took 0.02 seconds, although normal use is through the CLI. Version 4.6.2 fixes Node hooks whose `package.json` contains a build script under npm 11, repairing a regression introduced by 4.6.1.
pre-commit 4.6.2 installed in 0.3 seconds, occupied 12 MB across 10 packages, imported in 0.02 seconds, and had 0 audit findings in our sandbox. Install it for quick, pinned checks across mixed toolchains; leave networked and slow jobs in CI, and budget for hook-environment downloads on every uncached machine.
We installed it
| Install | ✓ · 0.3s | 10 packages on disk · 12 MB |
| Import | ✓ | import pre_commit in 0.02s · pure Python · requires Python >=3.10 |
| Known vulns | 0 | (pip-audit) |
Answers from our run
Does pre-commit install cleanly?
Yes. In a fresh container with an empty cache, pip install pre-commit finished in 0.3s, leaving 10 packages and 12 MB on disk. pip-audit reported no known vulnerabilities.
What does pre-commit need to run?
Python >=3.10, and nothing compiled: it is pure Python. In our run import pre_commit succeeded in 0.02s.
pre-commit or prek: which should you use?
prek: Use it when a Rust implementation and broad pre-commit configuration compatibility are the priority. pre-commit 4.6.2 installed in 0.3 seconds, occupied 12 MB across 10 packages, imported in 0.02 seconds, and had 0 audit findings in our sandbox.
When should you not use pre-commit?
Contributors will not install hooks and CI already enforces one fast task command. Another YAML layer may add setup without changing what gets caught.
Discussed on
- hnPrek: A better, faster, drop-in pre-commit replacement, engineered in Rust291 points
- hnPre-commit hooks are broken212 points
- hnYipit Django Blog: Why You Need a Git Pre-Commit Hook108 points
- hnPre-commit: framework for managing/maintaining multi-language pre-commit hooks84 points
- hnShow HN: Pre-commit – A framework for managing multi-language pre-commit hooks73 points
Use it if
- One repository needs pinned, repeatable formatters and validators across Python, Node, Go, Ruby, Rust, or other supported hook languages.
- Fast checks should receive only staged filenames during a commit and run over every file in CI.
- Developers should get the same hook revision without installing each linter globally.
- Commit messages, pushes, merges, or an explicit manual stage need checks beyond the ordinary pre-commit event.
- Contributors will not install hooks and CI already enforces one fast task command. Another YAML layer may add setup without changing what gets caught.
- The checks require remote services, containers, or several minutes. Put them in CI or pre-push before routine commits teach developers to use `--no-verify`.
- Developers must work offline on fresh clones. The first run clones hook repositories and may download interpreters or language packages unless caches are prewarmed.
- Hooks are expected to inspect unstaged edits. pre-commit temporarily stashes conflicting working-tree changes so a commit-stage hook sees the staged snapshot.
- A Node-only project wants thin native hook scripts and staged package commands. Husky plus lint-staged avoids Python bootstrap and multi-language environment management.
- The team wants one binary compatible with most pre-commit configs and faster cold starts. The Rust-based `prek` package is the closer comparison.
Setup reality
We installed pre-commit 4.6.2 in a fresh Python 3.12 Bookworm sandbox in 0.3 seconds. It left 10 packages and 12 MB on disk. pip-audit found 0 known vulnerabilities. The pure-Python distribution has 5 direct dependencies, requires Python 3.10 or newer, uses the MIT license, and does not include py.typed. import pre_commit succeeded in 0.02 seconds, but the supported user interface is the pre-commit command and its YAML file.
Each clone needs pre-commit install to place scripts under .git/hooks; CI can call pre-commit run --all-files without installing those scripts. A configured rev should be an immutable tag or commit. pre-commit autoupdate edits revisions, so review that diff and the resulting formatter changes together. Installing the framework alone supplies no checks. Every hook ID must come from a configured remote repository or repo: local.
Cold hook execution is slower than the 0.3-second framework install. pre-commit clones every remote hook and creates a cached environment for its language and revision. Python hooks build virtual environments, while Node hooks can install their own runtime and npm packages. Cache PRE_COMMIT_HOME in CI with the Python version and config hash in the cache key. language: system skips isolation but depends on whatever executable happens to be on PATH.
During pre-commit, the framework targets staged files and stashes conflicting unstaged edits. A formatter can modify a file and fail the commit so the new bytes can be reviewed and staged. Hooks receive filename batches unless pass_filenames: false changes the contract; require_serial: true prevents parallel batches. Extra stages need both YAML configuration and the matching pre-commit install --hook-type ... call. Local SKIP=id exceptions should not disable the equivalent CI requirement.
Patterns
Attach the hook to one clone install-pre-commit-hook
pre-commit install`pre-commit install` writes the Git hook for the current clone. Other clones and CI jobs do not inherit that `.git/hooks` file.
Configure immutable hook revisions pin-remote-hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespaceA hook `rev` should resolve to an immutable tag or commit. The framework installs the listed hook repository; it does not bundle these checks itself.
Check every tracked file run-entire-repository
pre-commit run --all-files`--all-files` is the usual CI invocation and the right verification after adding or updating hooks. A normal commit run selects staged files instead.
Execute one hook on named files run-selected-hook
pre-commit run check-yaml --files config/app.yaml deploy/service.ymlThe positional name is the configured hook ID. `--files` bypasses staged-file selection and passes only the paths that match the hook's filters.
Update revisions and test the result review-hook-updates
pre-commit autoupdate
git diff -- .pre-commit-config.yaml
pre-commit run --all-files`autoupdate` edits the config. Review revision changes before accepting any repository-wide formatting or rule changes produced by the new hook versions.
Target one directory and file type filter-hook-files
repos:
- repo: local
hooks:
- id: validate-api
name: validate API schemas
entry: python scripts/validate_api.py
language: python
files: ^api/.*\.ya?ml$
exclude: ^api/generated/`files` and `exclude` are regular expressions searched against repository-relative paths. pre-commit applies them before invoking the hook.
Give a local Python hook its own dependencies define-isolated-local-hook
repos:
- repo: local
hooks:
- id: validate-toml
name: validate project TOML
entry: python scripts/check_toml.py
language: python
additional_dependencies:
- tomli==2.2.1
types: [toml]`language: python` creates an isolated environment and installs `additional_dependencies`. Pin those packages because they are outside the project's main environment.
Disable filename arguments for a test command run-whole-project-command
repos:
- repo: local
hooks:
- id: unit-smoke
name: unit smoke tests
entry: python -m pytest -q tests/smoke
language: system
pass_filenames: false`pass_filenames: false` stops pre-commit from appending selected paths. `language: system` uses the caller's PATH and gives up an isolated tool environment.
Enable commit and message hooks install-commit-message-stage
pre-commit install \
--hook-type pre-commit \
--hook-type commit-msgListing `stages: [commit-msg]` in YAML does not create `.git/hooks/commit-msg`. Install every required Git hook type in each clone.
Move an expensive job to a manual stage configure-manual-check
repos:
- repo: local
hooks:
- id: integration
name: integration tests
entry: ./scripts/integration.sh
language: system
pass_filenames: false
stages: [manual]
# Explicit invocation:
# pre-commit run integration --hook-stage manual --all-filesA manual-stage hook never runs during an ordinary commit. CI or the release process must invoke it explicitly if passing it is required.
Use a stable CI cache directory cache-hook-environments
export PRE_COMMIT_HOME="$CI_PROJECT_DIR/.cache/pre-commit"
pre-commit run --all-filesCache this directory with keys that include the Python version and a hash of `.pre-commit-config.yaml`. Hook revisions and language runtimes affect compatibility.
Record an exceptional local skip skip-local-hook-once
SKIP=validate-api git commit -m 'Update generated schema fixture'`SKIP` accepts comma-separated hook IDs for that process. Required CI checks should still run so a workstation exception cannot become repository policy.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| prek | PyPI | Use it when a Rust implementation and broad pre-commit configuration compatibility are the priority. |
| husky | npm | Use it for lightweight native Git hook scripts in a repository already centered on Node. |
| lint-staged | npm | Use it to feed staged files into package commands, commonly from a Husky hook. |
More cli & tooling guides
commander · chalk · typescript · esbuild · yargs · click · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

