Setup Pre-commit Hooks
Set up fast pre-commit hooks that catch problems before they land — detect the repo's existing stack and hook mechanism, run lint/format/typecheck plus a secret scan on staged files only, keep the slow test suite in CI, and make the setup reproducible for the whole team.
/setup-precommit-hooksnpx agentscamp add commands/setup-precommit-hooksInstall to ~/.claude/commands/setup-precommit-hooks.md
A slash command that installs fast, staged-only pre-commit hooks: it detects the project's stack and existing hook mechanism (pre-commit framework, Husky + lint-staged, or a native git hook), wires lint/format/typecheck plus a secret scan over staged files, keeps the full test suite in CI, auto-fixes formatting, commits the config, and reports the one-time install command teammates run.
Scope
No arguments. Your job: leave this repo with pre-commit hooks that run in seconds, only on staged content, blocking the cheap mistakes (lint errors, unformatted code, type breaks, committed secrets) before they enter history — while the full test suite stays in CI.
Match the tooling the repo already uses. Do not impose a new framework on a repo that has a working one, and do not introduce a second runner alongside an existing one.
WARNING
Hooks that run the whole test suite on every commit are slow, so developers learn to type --no-verify and the hooks stop protecting anything. Keep the commit path under a few seconds. Slow, comprehensive checks belong in CI.
Step 1 — Detect the stack and what already exists
Before writing anything, read the ground truth:
- Existing hook mechanism —
.pre-commit-config.yaml(the pre-commit framework),.husky/+ alint-stagedblock inpackage.json,lefthook.yml, or a hand-rolled.git/hooks/pre-commit. Also checkgit config core.hooksPath. - Stack —
package.json,pyproject.toml/requirements.txt,go.mod,Cargo.toml,Gemfile. - Tools the repo already has — linter (eslint, ruff, golangci-lint, clippy), formatter (prettier, ruff format/black, gofmt, rustfmt), type checker (tsc, mypy, pyright), and how the test suite is invoked.
Reuse those exact tools and their existing config. The hook should call the same eslint/ruff/prettier the team already runs, not a new one with different rules.
Step 2 — Pick the mechanism (match, don't impose)
- A config already exists → extend it. Add missing checks to the current
.pre-commit-config.yaml/lint-stagedblock. - JS/TS repo, nothing yet → Husky + lint-staged (
lint-stagedalready runs only on staged files — that's the whole point). - Python or polyglot repo, nothing yet → the
pre-commitframework (.pre-commit-config.yaml); it pins hook versions and handles staged-only runs. - Tiny/no package manager → a native
.git/hooks/pre-commitscript. Note that native hooks aren't shared by git, so Step 5 must check it into the repo and add an install step.
State your choice and why in one line.
Step 3 — Configure fast, staged-only checks
Wire these against staged files only, fastest-failing first:
- Secret scan — block committed credentials with
gitleaks protect --stagedor pre-commit'sdetect-secrets. This is the one check worth running first; a leaked key can't be un-pushed. - Format (auto-fix) — run the formatter in write mode on staged files, then re-stage them (
prettier --write,ruff format,gofmt -w). Auto-fixing beats rejecting the commit over whitespace. - Lint — only the staged files (
eslint,ruff check,golangci-lint run); enable--fixwhere the linter's fixes are safe. - Typecheck — only if it's fast on the changed scope.
tscis whole-project and often too slow for the commit path; if so, leave it to CI rather than degrading the commit experience.
With lint-staged, the staged-file list is passed to each command automatically. With the pre-commit framework, set pass_filenames: true (the default) and scope with files:/types:.
WARNING
The hook must operate on staged content only. If a tool reads the working tree instead of the index, a developer can stage a clean version, leave a broken version unstaged, and the hook passes on code that won't be what's committed. lint-staged and the pre-commit framework stash unstaged changes to avoid exactly this — a raw native hook does not, so handle it explicitly there.
Step 4 — Keep the slow stuff in CI
Do not put the full test suite, full-repo typecheck, or a full build in the commit hook. Confirm those run in CI (check .github/workflows/); if a needed check is missing there, name the exact job that should run it (lint, typecheck, full tests on push/PR) and flag that it belongs in CI, not the commit path. A pre-push hook is the acceptable home for a fast smoke subset — never a substitute for CI.
Step 5 — Make it reproducible for the team
A hook that only works on your machine is worthless. Ensure:
- The config file is committed (
.pre-commit-config.yaml, thelint-stagedblock,.husky/scripts, or the checked-in native hook + an installer likegit config core.hooksPath .githooks). - There is one install command a teammate runs after cloning —
npx husky(wired via thepreparescript inpackage.jsonsonpm installdoes it), orpre-commit install. - Hook tool versions are pinned (pre-commit
rev:tags; devDependencies for JS) so everyone runs identical checks.
Verify it actually fires: stage a deliberately broken file and confirm the commit is rejected, then fix and confirm it passes.
Step 6 — Document the escape hatch
Note in the config or a short README line that git commit --no-verify skips hooks for genuine emergencies (hotfix, mid-rebase WIP). Don't hide it — but pair it with the reminder that CI still enforces the same checks, so bypassing locally only defers the failure.
Report
End with:
- Files written/changed — config path(s) and any
package.jsonscript additions. - One-time install command teammates run after cloning (exact command).
- What runs on commit vs. what's left to CI, and the bypass flag for emergencies.
Related
- Setup Claude CIWire Claude Code into this repo's CI the safe way — install the GitHub App or scaffold the workflow YAML, scope permissions to the minimum, set secrets correctly, and verify with a real trigger.
- Scaffold DockerfileScaffold a production-grade multi-stage Dockerfile and .dockerignore for the current project.
- Running Claude Code in CI: Headless Mode & GitHub ActionsClaude Code without the terminal — claude -p flags, JSON and structured output, safe permission scoping, and the official GitHub Action responding to @claude.
- Security ScanScan the current diff or given paths for security vulnerabilities.
- Scaffold GitHub ActionScaffold a hardened GitHub Actions workflow for a stated goal, wired to the project's real test/lint/build commands.