Claude Code hooks are the most underused power feature in AI-assisted development. They let you intercept every tool call with custom shell scripts — auto-formatting code, blocking destructive git commands, enforcing project conventions, and building safety guardrails that run automatically. Here’s how to set them up.
Claude Code hooks let you run custom shell scripts before and after every tool call — and most developers haven’t set them up yet. Hooks are the difference between using Claude Code as a chatbot and using it as an autonomous engineering system with guardrails. They intercept tool executions (file edits, bash commands, commits) and run your own validation logic, giving you control over what Claude does without having to watch every action. If you’re using Claude Code without hooks, you’re leaving the most powerful safety and automation features on the table.
What Are Claude Code Hooks?
Hooks are shell commands that execute automatically in response to Claude Code events. There are several hook types:
- PreToolUse: Runs before a tool executes. Can block the action if the script exits with a non-zero code.
- PostToolUse: Runs after a tool completes. Perfect for auto-formatting, linting, or logging.
- UserPromptSubmit: Runs when you submit a prompt. Can inject context or modify behavior.
Hooks are configured in your .claude/settings.json file or in project-level .claude/settings.local.json. Each hook specifies a matcher (which tool it applies to) and a command to execute.
Setting Up Your First Hook: Auto-Lint on Every Edit
The most immediately useful hook is auto-linting. Every time Claude edits a file, your linter runs automatically:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"command": "cd $PROJECT_DIR && npx biome check --fix $FILE_PATH 2>/dev/null || true"
}
]
}
}This hook matches any Edit or Write tool call and runs Biome (or ESLint, Prettier, whatever your project uses) on the changed file. The || true ensures the hook doesn’t block Claude if linting fails — it just fixes what it can. The result: every file Claude touches is automatically formatted to your project’s standards, without you having to ask.
Safety Hook: Block Dangerous Commands
The most critical hook is a PreToolUse safety guard that prevents Claude from running destructive commands:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"command": "echo \"$TOOL_INPUT\" | grep -qE \"(rm -rf|git push --force|git reset --hard|DROP TABLE|docker system prune)\" && echo \"BLOCKED: Dangerous command detected\" && exit 1 || exit 0"
}
]
}
}This hook intercepts every Bash command and checks for destructive patterns. If it finds rm -rf, git push --force, git reset --hard, or similar dangerous operations, it blocks the execution before it happens. This is especially important when using Claude Code in autonomous mode where it makes decisions without asking.
Auto-Commit Hook: Structured Git History
You can hook into commit operations to enforce commit message conventions:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"command": "echo \"$TOOL_INPUT\" | grep -q \"git commit\" && cd $PROJECT_DIR && git log -1 --pretty=%s | grep -qE \"^(feat|fix|refactor|test|docs|chore|perf)\\(\" || echo \"Warning: commit message doesn’t follow conventional format\" || true"
}
]
}
}Context Injection with UserPromptSubmit
The UserPromptSubmit hook lets you inject context into every conversation. This is powerful for setting project-specific context that Claude should always have:
{
"hooks": {
"UserPromptSubmit": [
{
"command": "echo \"Current branch: $(git branch --show-current). Last commit: $(git log -1 --oneline). Modified files: $(git diff --name-only HEAD | head -5)\""
}
]
}
}Every time you submit a prompt, Claude receives the current git branch, last commit, and modified files as additional context. This means Claude always knows where you are in the codebase without you having to explain it.
Advanced: Multi-Agent Safety Hooks
If you’re using Claude Code’s agent teams (multi-agent coordination where sub-agents work in parallel), hooks become even more critical. Each agent can be configured with its own hooks to enforce boundaries:
- Read-only agents: PreToolUse hook that blocks Edit/Write/Bash for research-only agents
- Scoped agents: PreToolUse hook that only allows edits to specific directories
- Auditing: PostToolUse hook that logs every agent action for review
WOWHOW sells pre-configured Claude Code agent team packs with safety hooks already built in — 12-agent specialist teams with safety guardrails, auto-lint, and structured workflows ready to deploy.
Environment Variables Available in Hooks
Every hook receives environment variables that let you make smart decisions:
$TOOL_NAME— which tool is being called (Edit, Write, Bash, etc.)$TOOL_INPUT— the full input to the tool (file path, command, etc.)$FILE_PATH— the file being edited (for Edit/Write hooks)$PROJECT_DIR— the project root directory
These variables let you write hooks that are context-aware. For example, you could block edits to production config files while allowing edits to test files, or only run expensive linting on TypeScript files.
Real-World Hook Configuration
Here’s a production-grade hook configuration that we use at WOWHOW for the storefront codebase:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"command": "/path/to/safety-guardian.sh"
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"command": "cd $PROJECT_DIR && npx biome check --fix $FILE_PATH 2>/dev/null; exit 0"
}
],
"UserPromptSubmit": [
{
"command": "echo \"Git: $(git branch --show-current) | $(git diff --stat HEAD | tail -1)\""
}
]
}
}The safety-guardian.sh script is a comprehensive safety check that blocks destructive operations, validates file paths, and logs all commands for audit. This is the same setup powering our 12-agent team configuration.
Common Mistakes to Avoid
Based on our experience running hooks in production:
- Don’t make hooks too slow. Every PreToolUse hook adds latency to every tool call. Keep them under 100ms. If you need heavy validation, run it in PostToolUse instead.
- Don’t block on non-critical failures. Use
|| trueorexit 0for non-critical PostToolUse hooks. A failing lint hook shouldn’t stop Claude from working. - Do use project-level settings. Put hooks in
.claude/settings.local.json(gitignored) rather than the shared.claude/settings.jsonunless the whole team should use them. - Do test hooks manually first. Run your hook command in the terminal with sample inputs before adding it to the configuration.
Getting Started
The fastest path to useful hooks:
- Create
.claude/settings.local.jsonin your project - Add the auto-lint PostToolUse hook (adapt to your linter)
- Add the safety PreToolUse hook (block dangerous commands)
- Run a few Claude Code tasks and verify hooks fire correctly
- Iterate: add more hooks as you discover patterns that need enforcement
Hooks transform Claude Code from an AI assistant into an autonomous engineering system with guardrails. Use our free token counter to estimate the cost of longer Claude Code sessions, and check out the developer tools collection for pre-built agent team configurations with hooks included.