Claude Code Hooks: Pre-Commit Lint Automation Guide (2026)
A hands-on guide to Claude Code hooks for pre-commit lint automation — block bad commits, auto-format code, and standardize a team's AI-assisted workflow.
You finally convinced your team to adopt Claude Code. A week later, you open the repo and find three different formatters ran on three different files, two console.log statements that slipped past review, and one commit message that just says “fix.” The AI is fast, but fast without guardrails is how you get drift. Claude Code hooks are the missing piece — they run your lint, format, and policy checks automatically before code leaves the session.
This guide walks through the setup for pre-commit lint automation, what to watch out for, and when hooks are the wrong answer.
TL;DR
Claude Code hooks are shell commands the harness executes in response to events (tool calls, session stop, user prompt submit). For pre-commit lint automation, you wire a PreToolUse hook on the Bash tool that intercepts git commit calls, runs ESLint/Prettier/Ruff, and blocks the commit if checks fail. Configuration lives in settings.json — either global (~/.claude/settings.json) or per-project (.claude/settings.json). Setup takes about 10 minutes and catches the vast majority of style drift before it hits your diff.
How Claude Code Hooks Actually Work
A hook is a shell command registered against an event name in settings.json. When the event fires, the Claude Code harness — not the model — runs the command with a JSON payload on stdin and decides what to do based on the exit code and stdout.
The four most useful event hooks for a lint workflow:
PreToolUse: fires before a tool call. Exit non-zero to block the call and feed the error back to the model.PostToolUse: fires after a tool call completes. Good for auto-formatting files that were just edited.UserPromptSubmit: fires on every user prompt. Useful for injecting project context or policy reminders.Stop: fires when the assistant turn ends. Good for final-status checks.
The critical insight: hooks run deterministically in the harness, which means the model cannot forget or skip them. That is exactly what you want for lint enforcement.
Deep Dive: Building the Pre-Commit Lint Hook
Here is a minimal but production-shaped setup for a Node/TypeScript project.
Step 1: Create the Hook Script
Put this in .claude/hooks/pre-commit-lint.sh at the repo root:
#!/usr/bin/env bash
set -euo pipefail
payload=$(cat)
cmd=$(echo "$payload" | jq -r '.tool_input.command // ""')
if [[ "$cmd" != *"git commit"* ]]; then
exit 0
fi
echo "Running lint before commit..." >&2
if ! npx eslint . --max-warnings=0 >&2; then
echo "BLOCKED: ESLint failed. Fix errors before committing." >&2
exit 2
fi
if ! npx prettier --check . >&2; then
echo "BLOCKED: Prettier check failed. Run 'npx prettier --write .' first." >&2
exit 2
fi
exit 0
Make it executable: chmod +x .claude/hooks/pre-commit-lint.sh.
The exit code 2 tells Claude Code to block the tool call and return stderr to the model — which will then read the lint output and fix the problem on its next turn. This is the loop you want.
Step 2: Register the Hook
Add to .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/pre-commit-lint.sh"
}
]
}
]
}
}
The matcher restricts this hook to Bash tool calls, so it does not fire on every Read or Edit.
Step 3: Add an Auto-Format PostToolUse Hook
Style drift is best fixed by the tool that caused it. Add a PostToolUse hook that runs Prettier on the file Claude just edited:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs -r npx prettier --write"
}
]
}
]
}
}
Now every file the model touches gets auto-formatted before the commit hook even runs — so the commit path rarely fails on pure style issues.
Pros and Cons of Hook-Based Enforcement
| Dimension | Hooks | Model-Side Prompting (CLAUDE.md) |
|---|---|---|
| Reliability | Deterministic, always runs | Model may forget on long turns |
| Debuggability | Shell output is explicit | Opaque — hard to tell what was considered |
| Setup cost | ~10 minutes per rule | Zero |
| Team adoption | Checked into repo, uniform | Depends on each dev’s context |
| Flexibility | Shell is fully expressive | Natural language is fuzzy |
| Failure mode | Hard block, surfaced clearly | Silent drift |
Trade-off to know: hooks add latency to every affected tool call. A 400ms ESLint run compounds fast. Keep hook scripts tight and fail fast.
Who Should Use This
- Teams shipping from Claude Code: Hooks are the cheapest way to enforce uniform output across AI-assisted developers.
- Open-source maintainers: Lock down formatting and commit conventions before PRs are even opened.
- Security-sensitive repos: Block
git push --force,rm -rf, or commits that touch.envwith aPreToolUsehook. - Solo developers on long projects: Hooks catch the drift your future self will otherwise have to untangle.
Skip hooks if your repo is a one-off script or a learning sandbox — the setup overhead outweighs the benefit.
FAQ
Do hooks slow down Claude Code?
Yes, but usually by milliseconds. A hook adds its own runtime plus ~50ms of harness overhead. Fast linters (Ruff, Biome) are barely noticeable; slow ones (full TypeScript project compile) are not hook material — run them in CI instead.
Can hooks block the model from doing something harmful?
Yes. A PreToolUse hook returning exit code 2 blocks the tool call and tells the model why. This is the primary safety pattern for rm -rf, force-push, and DB migration commands.
Where should settings.json live — global or project?
Project-level (.claude/settings.json, checked into git) for team rules. Global (~/.claude/settings.json) for your personal preferences. Project settings override global.
What if a hook has a bug and blocks everything?
Edit or delete .claude/settings.json directly. The harness re-reads settings each session, so a fix takes effect immediately.
Can hooks replace a pre-commit git hook (Husky, lefthook)?
They complement each other. Claude Code hooks catch issues inside the AI session; git hooks catch them from any developer. In a mixed team, run both and let the cheaper one fail first.
Bottom Line
Claude Code hooks turn “hope the model remembers” into “the harness enforces it.” A ten-minute PreToolUse setup eliminates a class of style-drift problems that would otherwise show up in every PR.
Next step: read our deep dive on CI/CD pipeline architecture to see how hook-level checks fit alongside your build pipeline, and API rate limiting design for patterns you can enforce via hooks. Official hook reference: docs.claude.com.
Product recommendations are based on independent research and testing. We may earn a commission through affiliate links at no extra cost to you.