Hooks
Hooks are shell commands you configure in settings.json that automatically run before or after Claude uses specific tools. Use them for linting, formatting, notifications, or blocking dangerous operations.
How Hooks Work (Visual)
This sequence diagram shows exactly what happens when Claude tries to edit a file and hooks are configured. Notice the two checkpoints — one before, one after.
Explain Like I'm 12
What are Hooks?
At the simplest level, hooks are shell commands that Claude Code runs automatically at specific moments. You don't invoke them manually. You configure them once, and they fire every time the matching event happens.
Think of them like Git hooks, but for Claude Code's tool usage. Every time Claude is about to use a tool (like editing a file or running a command), your hook gets a chance to intervene. And every time a tool finishes, another hook can react to the result.
This gives you a powerful automation layer on top of Claude Code. You stay in control without having to babysit every action.
Hook Types
There are exactly two hook types. That's it. Simple.
PreToolUse
Fires before Claude executes a tool. This is your chance to inspect, validate, or block the action. If your hook exits with a non-zero code, the tool is blocked and Claude gets told "nope."
Use cases: checking if a file should be edited, validating a command before it runs, logging what's about to happen.
PostToolUse
Fires after Claude executes a tool. The action already happened, so you can't block it — but you can react to it. Run formatters, send notifications, update logs, whatever you need.
Use cases: auto-formatting code after edits, running tests after file changes, sending a Slack message after a commit.
Configuration
Hooks live in your settings.json file. The structure is straightforward: you have a top-level "hooks" key, and under it you define arrays for PreToolUse and PostToolUse.
Each hook entry has two fields:
matcher— which tool this hook applies to (e.g.,"Edit","Bash")command— the shell command to run
Here's a complete example:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit",
"command": "echo 'About to edit...'"
}
],
"PostToolUse": [
{
"matcher": "Edit",
"command": "npx prettier --write $FILE"
}
]
}
}
That's the whole thing. When Claude is about to edit a file, it echoes a message. After the edit, it auto-formats with Prettier. Clean and declarative.
The Matcher
The matcher field tells Claude Code which tool triggers the hook. You can match specific tools by name:
"Edit"— matches the Edit tool"Bash"— matches the Bash tool"Read"— matches the Read tool
You can also use glob patterns for broader matching:
# Match any tool
"*"
# Match tools starting with "File"
"File*"
If a hook's matcher matches the tool Claude is about to use (or just used), the hook fires. Otherwise, it's silently skipped. No overhead, no noise.
Real-World Examples
Let's look at some practical hooks you might actually use in a real project.
1. Auto-format with Prettier after edits
This is probably the most common hook. Every time Claude edits a file, Prettier cleans it up automatically.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit",
"command": "npx prettier --write $FILE"
}
]
}
}
2. Run linter before Bash commands
Catch problems before they happen. This hook runs ESLint on your source before Claude executes any bash command.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"command": "npx eslint src/ --quiet"
}
]
}
}
3. Block certain file patterns from being edited
Don't want Claude touching your lock files or generated code? Block it.
# In a script referenced by your hook:
if echo "$FILE" | grep -qE '(\.lock|\.generated\.)'; then
echo "Blocked: Cannot edit lock or generated files"
exit 1
fi
Set the hook's command to run that script, and any edit to a matching file gets blocked (because of the non-zero exit code).
4. Send Slack notification after commits
Want your team to know when Claude commits something? Hook into it.
# post-commit-notify.sh
curl -X POST -H 'Content-type: application/json' \
--data '{"text":"Claude just made a commit!"}' \
"$SLACK_WEBHOOK_URL"
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"command": "bash ./scripts/post-commit-notify.sh"
}
]
}
}
Tips & Gotchas
Things to keep in mind
- Hooks run synchronously. Claude waits for your hook to finish before moving on. Keep them fast, or Claude will feel slow.
- Non-zero exit codes block the tool (PreToolUse only). If your PreToolUse hook exits with code 1 (or any non-zero), the tool is blocked. PostToolUse hooks don't block anything — the action already happened.
- Environment variables are available. Hooks receive context like
$FILEand$TOOL_NAMEso you can write conditional logic. - Test your hooks manually first. Run the command in your terminal before putting it in
settings.json. Debug once, not during a Claude session. - Order matters. If you have multiple hooks for the same matcher, they run in the order they appear in the array.
Test Yourself
Q: Where do you configure hooks?
settings.json, under the "hooks" key.Q: What are the two hook types?
Q: Can a PreToolUse hook block a tool from running?
Q: Give one real-world use case for hooks.