prek
prek is a Rust command-line manager for Git hooks and a practical alternative to Python's pre-commit. It reads existing .pre-commit-config.yaml files, installs isolated hook tools and runtimes, runs checks on staged files or in CI, and installs Git hook shims. It also adds a native prek.toml format, built-in Rust versions of common checks, shared toolchain caches, concurrent hook priorities, managed runtime downloads, and workspace discovery for monorepos with nested configurations.
prek is the most compelling pre-commit replacement when environment setup time or monorepo coordination is a real cost. Keep pre-commit when exact upstream behavior and mature interface stability matter more than speed, and avoid all remote hook managers unless you are prepared to review the code they execute.
Use it if
- Your repository already uses pre-commit configuration and environment setup or hook startup is noticeably slow
- Your polyglot team wants one binary to provision hook runtimes without requiring Python just to run the manager
- Your monorepo needs separate nested hook configurations, scoped file sets, and concurrent execution for independent projects
- You want offline built-in checks for common whitespace, syntax, filename, secret-key, and merge-conflict mistakes
- You need exact pre-commit behavior: prek documents a file-identification divergence, does not implement pre-commit hazmat, and prek-only TOML, builtins, glob mappings, and workspaces are not upstream-compatible
- Your organization requires a stable 1.x interface before adopting developer infrastructure: the current release is 0.4.12 and the command tree and native features are still expanding quickly
- Your existing pre-commit checks are fast and reliable: replacing commands, reinstalling every developer's Git shim, and changing CI creates churn without a meaningful payoff
- You cannot allow first-run network downloads or execution of repository-supplied code: remote hooks clone repositories and may install managed Python, Node.js, Bun, Deno, Go, Rust, Ruby, or other environments
- You only need to run JavaScript linters on staged files in a Node project: lint-staged plus your package manager avoids adopting the larger cross-language hook repository and toolchain model
Setup reality
The PyPI package is a delivery wrapper for a compiled Rust executable, not a Python application. Version 0.4.12 publishes wheels for many Linux, macOS, and Windows targets and declares Python 3.8 or newer for that installation route, with no Python runtime dependencies. Other supported routes include uv, pipx, Homebrew, npm, Cargo, Nix, Conda, Scoop, Winget, and standalone installers. If no binary matches and you build from source, the README requires Rust 1.95 or newer. Git is mandatory, and real hooks still need their own tools. The first run clones hook repositories, resolves revisions, downloads managed runtimes where configured, and creates environments under ~/.cache/prek, so it is much slower and more network-dependent than later runs. Cache that directory in CI only when cache keys include operating system, architecture, prek version, and config content. A remote hook is executable supply-chain code. Pin reviewed tags or commit SHAs, use update checks deliberately, and do not treat a hook repository as harmless configuration. Existing pre-commit users can keep YAML, replace commands, then run prek install -f once to replace the old shim. Without -f, an existing hook can block installation; with it, you are intentionally taking ownership of that hook file. prek run checks staged files by default, while CI normally needs --all-files or an explicit range. Hooks that modify files exit nonzero so the developer can inspect and restage changes. Workspace discovery walks nested configuration files, respects ignore rules, and caches the result; a newly added project may require --refresh. Parent and child projects can both process the same file unless the child is orphaned. Concurrent hooks must not mutate shared lockfiles, caches, or sibling project state. Keep YAML and upstream-compatible features if developers may switch back to pre-commit; prek.toml, repo: builtin, workspace mode, and prek glob mappings commit the repository to prek.
Patterns
Configure two hooks in prek.tomlconfigure-basic-hooks
[[repos]]
repo = "https://github.com/pre-commit/pre-commit-hooks"
rev = "v6.0.0"
hooks = [
{ id = "check-yaml" },
{ id = "end-of-file-fixer" },
]prek.toml is native to prek and will not run with upstream pre-commit. Keep YAML when portability matters.
Run an existing pre-commit YAML configurationreuse-precommit-config
prek validate-config .pre-commit-config.yaml
prek run --all-filesCommon configurations work unchanged, but check the compatibility page for behavioral differences and unsupported hazmat commands.
Install the Git hook shiminstall-git-hook
prek installIf pre-commit already owns the hook, the migration guide calls for prek install -f once. Review the existing hook before forcing replacement.
Uninstall the managed Git hookremove-git-hook
prek uninstallThis removes prek's integration, not the tool environments cached under ~/.cache/prek.
Run hooks against staged filesrun-staged-files
git add src tests
prek runFixing hooks may modify files and return nonzero. Inspect and stage the modified files before committing again.
Run every hook against every tracked filerun-entire-repository
prek run --all-filesUse this for initial adoption and ordinary CI. A plain prek run is centered on the staged-file workflow.
Run one hook on explicit filesrun-selected-files
prek run check-yaml --files .github/workflows/ci.yml config/app.ymlThe hook's own files, exclude, and type filters still determine whether each explicit path is eligible.
Preview hooks and files without executingpreview-selection
prek run --all-files --dry-runDry-run helps debug selection, but it does not prove that tool environments install or hooks pass.
Use Rust-native hooks without a remote repositoryuse-offline-builtins
repos:
- repo: builtin
hooks:
- id: trailing-whitespace
- id: check-merge-conflict
- id: detect-private-keyrepo: builtin avoids cloning and environment setup, but makes the YAML incompatible with upstream pre-commit.
Fail CI when hook revisions need attentioncheck-hook-updates
prek update --checkThis previews updates and exits nonzero without rewriting configuration, which is safer than automatic unreviewed upgrades in CI.
Target one monorepo project and skip a hookrun-workspace-project
prek run frontend/ --skip frontend:formatThe trailing slash marks a project selector. Without it, prek interprets the token as a hook ID.
Inspect and prune cached environmentsinspect-cache
prek cache dir
prek cache size
prek cache gc --dry-run
prek cache gcgc removes unused repositories, environments, and toolchains. Use dry-run first on developer or shared CI caches.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| pre-commit | PyPI | You prioritize the original implementation, strict configuration portability, and the largest established hook-manager community |
| lefthook | PyPI | You want a fast compiled hook runner with its own simpler configuration and do not need pre-commit repository compatibility |
| lint-staged | npm | You have a Node-centered repository and only need package scripts run against staged files |