setup-pre-commit-hooks
Set up pre-commit hooks for formatting, linting, and building using proper tooling with git-tracked config files.
Process
Step 0: Infer stack and hook management tool
Examine the repo to determine:
-
Stack - Identify from existing config files (e.g.,
Cargo.toml→ Rust,package.json→ Node)- For Node/TypeScript monorepos: also check that all workspace packages use the same version of shared tools (eslint, prettier, typescript). Mismatched versions (e.g., eslint 9 in one package, eslint 10 in another) require separate lint-staged patterns and should be fixed first.
-
Git root - Before configuring any hook tooling, identify the actual git root. In nested worktree setups a subdirectory like
project/may have its own.gitfile and be a completely separate repo — hook tooling installed there configures that repo's hooks, not the parent's. Rungit -C <dir> rev-parse --git-dirto confirm. -
Hook management tool - Choose based on ecosystem:
- Node/TypeScript: Use husky + lint-staged
- Python: Use pre-commit
- Go: Use lefthook
- Other: Pick a proper tool for the ecosystem. The important thing is that you do pick a proper tool instead of hand-rolling Git hooks.
-
Hook commands - Infer from existing tooling (package.json scripts, linter configs like
.eslintrc,ruff.toml). Default to at least these:- Formatting — run prettier on all file types it supports, not just source files. For lint-staged:
"**/*.{js,ts,jsx,tsx,json,md,yaml,yml,css,scss,html}": "prettier --write". - Linting
- Type-checking (for languages such as Python that won't otherwise encounter type-checking)
- TypeScript: use
tsc --noEmit(type-check only). Full builds belong in CI, not pre-commit. - TypeScript + lint-staged:
tsccannot receive individual staged file paths —tsc -p tsconfig.json file.tsthrows TS5042, andtsc --noEmit file.tsignores tsconfig compilerOptions.tsc-filesis abandoned and broken with TypeScript 6+. The correct solution is alint-staged.config.js(notpackage.json) using function syntax so tsc runs project-wide without file args:"**/*.ts": () => "pnpm --filter <pkg> typecheck". See: https://github.com/lint-staged/lint-staged/issues/825#issuecomment-2393146853
- TypeScript: use
- Building (for languages such as Rust that have a compiled build step — not TypeScript)
Do NOT include tests in pre-commit hooks - tests should run in CI to keep commits fast. Pre-commit hooks should only include fast checks that validate code quality, not test suites.
- Formatting — run prettier on all file types it supports, not just source files. For lint-staged:
Only ask the user if the stack is genuinely ambiguous (e.g., multiple languages with no clear primary).
Step 1: Install hook management tool and create config
Install the hook tool and create git-tracked config files.
Important: If you're using pre-commit, use current, non-deprecated hook IDs and syntax. Check official documentation for the latest hook names:
- For ruff: use
ruff-check(not the legacyruffalias)
Examples (non-exhaustive):
- Node (husky):
npm install --save-dev husky lint-staged && npx husky init - Python/Rust (pre-commit): Create
.pre-commit-config.yaml, thenpre-commit install && pre-commit install --hook-type post-commit - Go (lefthook): Create
lefthook.yml, thenlefthook install
If the config includes post-commit hooks (like cleanup tasks), install those hook types explicitly. For pre-commit framework: pre-commit install --hook-type post-commit.
Step 2: Verify hooks work
Run hooks manually to ensure they successfully pass and fail before proceeding. If hooks don't successfully fail, that means there's a problem with your setup because the hooks aren't actually catching anything.
Examples:
- husky:
npx lint-staged && npm run build - pre-commit:
pre-commit run --all-files - lefthook:
lefthook run pre-commit
Fix any failures before continuing.
Step 3: Document in README
Add a section documenting:
- What hooks run (formatting, linting, building)
- Setup command for new contributors
- Manual run command
Step 4: Commit changes
Ask for user confirmation, then commit all hook-related files (config files, package.json changes, README updates).
Key Principles
- Never hand-roll hooks - Always use proper tooling with git-tracked config
- Infer, don't ask - Determine stack and commands from existing repo files.
- Verify before committing - Hooks must successfully pass and fail before finalizing setup