# Pre-commit Hooks
Git hooks that run before each commit to enforce code quality, formatting, and security checks.
## Exit code controls behavior
The hook's **exit code** determines what happens:
- **Exit 0** → commit proceeds (even if files were modified)
- **Exit non-zero** → commit aborted
This creates three behavior types:
| Type | Modifies files? | Exit code | Effect | Examples |
|------|:-:|:-:|--------|---------|
| Auto-fix | Yes | 0 | Files re-staged, commit proceeds | Black, Prettier, isort |
| Fix-then-fail | Yes | non-zero | Commit fails — review changes, re-commit | Custom hooks for deps/security |
| Check-only | No | non-zero | Commit fails — fix manually | flake8, mypy, bandit |
**Fix-then-fail** is the key insight: a hook can modify files AND still fail the commit, forcing you to review the changes before re-committing. The [pre-commit framework docs](https://pre-commit.com/) don't clearly explain this — it's learned from source code and community knowledge.
Use fix-then-fail for critical files: dependency manifests, security configs, build scripts, database migrations.
## Frameworks
- **[Lefthook](https://github.com/evilmartians/lefthook)** — fast, zero-dependency, written in Go. Config in `lefthook.yml`. Recommended for new projects.
- **[pre-commit](https://pre-commit.com/)** — Python-based, large ecosystem of community hooks. Config in `.pre-commit-config.yaml`.
Both run linters, formatters, and custom scripts on staged files.
See also: [[JDF Hooks]] (curated hooks for common projects), [[Python Code Quality]]