Imagine you just asked Claude Code to edit a Python file. It makes the changes — but your team requires all Python files to be formatted with Black before committing. You’d normally have to remember to run Black yourself. Every. Single. Time.
Hooks fix that. A hook is a small automated action that Claude Code runs before or after it does something — like formatting your file the moment Claude finishes editing it. You set it up once and it runs silently in the background forever. No reminders. No manual steps. No forgotten formatting.
This guide covers everything you need to know about Claude Code hooks — what they are, how they work, and how to set up your first one today.
TL;DR
– Hooks are automated scripts triggered by Claude Code’s actions
– Three types: PreToolUse (before), PostToolUse (after), Stop (when task finishes)
– Configured in a hooks.json file — no complex coding required
– Common uses: auto-format, auto-lint, run tests, desktop notifications
– Set up once, runs forever — the ultimate Claude Code time-saver
Table of Contents
- What Are Claude Code Hooks?
- How the Claude Code Lifecycle Works
- The Three Hook Types
- Your First Hook — Step by Step
- Hook Matchers — Target Specific Files or Tools
- Real-World Hook Examples
- hooks.json — Full Configuration Reference
- Common Beginner Mistakes
- Quick Reference Cheatsheet
- What to Learn Next
What Are Claude Code Hooks?
A hook in Claude Code is an automated script that fires at a specific point in Claude’s workflow — just before it uses a tool, just after, or when it finishes a task entirely.
Think of hooks like the “if this, then that” logic in a no-code automation tool. “When Claude edits a Python file → automatically run my formatter.” You define the rule once. Claude handles the rest.
Without hooks, you manually run formatters, linters, and tests after every Claude session. With hooks, those steps happen automatically — Claude finishes its work, your rules run, your codebase stays clean.
Hooks live in a file called hooks.json — a simple configuration file where you describe what to run and when.
💡 Pro Tip: Hooks are project-specific by default. You can also set global hooks that run across every project. More on that in the configuration section.
How the Claude Code Lifecycle Works
To understand hooks, you need to understand what happens every time Claude Code takes an action. Claude operates in a loop:
- You give an instruction — “refactor this function”
- Claude plans — it figures out which tools it needs
- Claude uses a tool — it edits a file, runs a command, reads a file
- Claude reports back — it tells you what it did
- Repeat until the task is complete
- Claude stops — task finished
Hooks plug into steps 3 and 6. You can intercept any tool use — before it happens, after it happens, or when everything is done.
[The Claude Code Lifecycle Explained]
The Three Hook Types
PreToolUse
PreToolUse hooks run before Claude uses a tool. This gives you the chance to inspect, block, or modify what Claude is about to do.
Use PreToolUse when you want to:
- Validate that Claude is editing the right file
- Block certain actions on protected files
- Log what Claude is about to do before it does it
PostToolUse
PostToolUse hooks run after Claude uses a tool. This is the most commonly used hook type.
Use PostToolUse when you want to:
- Auto-format a file after Claude edits it
- Run a linter after Claude writes code
- Trigger a test after Claude modifies a function
Stop
Stop hooks run when Claude finishes the entire task — not after each individual tool use, but at the very end.
Use Stop hooks when you want to:
- Send a desktop notification when Claude is done
- Run your full test suite after a session
- Trigger a deployment after Claude completes a feature
[PreToolUse Hooks Explained]
[PostToolUse Hooks Explained]
[Stop Hooks Explained]
Your First Hook — Step by Step
Let’s set up a PostToolUse hook that automatically formats Python files with Black every time Claude edits one.
What you need:
- Claude Code installed
- Black installed (
pip install black) - A Python project
Step 1: Find your hooks.json location
Hooks live in your project’s .claude/ folder. If it doesn’t exist yet, create it.
# Create the .claude folder if it doesn't exist
mkdir -p .claude
Step 2: Create hooks.json
# Create the hooks configuration file
touch .claude/hooks.json
Step 3: Add your first hook
Open hooks.json and paste this:
{
"hooks": [
{
"type": "PostToolUse",
"matcher": {
"tool": "edit_file",
"file_pattern": "*.py"
},
"command": "black {file}"
}
]
}
What this does:
type: PostToolUse— runs after Claude uses a toolmatcher— only triggers when Claude edits a.pyfilecommand— runsblackon the file that was just edited
Step 4: Test it
Ask Claude Code to edit any Python file. The moment it finishes, Black runs automatically.
💡 Pro Tip: Always test a new hook on a non-critical file first. A misconfigured command can cause errors mid-session.
Hook Matchers — Target Specific Files or Tools
Matchers tell Claude Code when to trigger a hook. Without matchers, a hook runs on every single action — which is usually not what you want.
Match by tool
{
"matcher": {
"tool": "edit_file"
}
}
Available tools to match: edit_file, read_file, run_command, search_files
Match by file pattern
{
"matcher": {
"file_pattern": "*.py"
}
}
Supports standard glob patterns: .py, src//.ts, tests/*.test.js
Match by both
{
"matcher": {
"tool": "edit_file",
"file_pattern": "src/**/*.ts"
}
}
This only triggers when Claude edits a TypeScript file inside the src/ folder. Both conditions must be true.
[Hook Matchers Explained]
Real-World Hook Examples
Auto-lint JavaScript after every edit
{
"hooks": [
{
"type": "PostToolUse",
"matcher": {
"tool": "edit_file",
"file_pattern": "*.js"
},
"command": "eslint --fix {file}"
}
]
}
Run tests after Claude edits a test file
{
"hooks": [
{
"type": "PostToolUse",
"matcher": {
"tool": "edit_file",
"file_pattern": "*.test.js"
},
"command": "jest {file} --no-coverage"
}
]
}
Desktop notification when Claude finishes
{
"hooks": [
{
"type": "Stop",
"command": "osascript -e 'display notification \"Claude Code finished\" with title \"Done\"'"
}
]
}
Mac only. For Windows use a PowerShell notification command instead.
Log every file Claude edits
{
"hooks": [
{
"type": "PostToolUse",
"matcher": {
"tool": "edit_file"
},
"command": "echo '{file} edited at $(date)' >> .claude/edit_log.txt"
}
]
}
[Auto-Format Python Files with a Hook]
[Auto-Lint JavaScript with a Hook]
[Desktop Notifications When Claude Finishes]
[Run Tests Automatically After Claude Edits]
hooks.json — Full Configuration Reference
{
"hooks": [
{
"type": "PreToolUse | PostToolUse | Stop",
"matcher": {
"tool": "edit_file | read_file | run_command | search_files",
"file_pattern": "glob pattern e.g. *.py or src/**/*.ts"
},
"command": "shell command to run — use {file} for the affected file",
"timeout": 30,
"on_error": "continue | stop"
}
]
}
Field reference:
| Field | Required | What it does |
|---|---|---|
| type | YES | When to trigger — PreToolUse, PostToolUse, or Stop |
| matcher | NO | Filters which actions trigger the hook |
| matcher.tool | NO | Only trigger for this tool |
| matcher.file_pattern | NO | Only trigger for files matching this pattern |
| command | YES | The shell command to run |
| timeout | NO | Seconds before the hook times out (default: 30) |
| on_error | NO | What to do if the hook fails — continue or stop |
Global hooks location:
For hooks that run across all projects, place hooks.json at:
- Mac/Linux:
~/.claude/hooks.json - Windows:
%APPDATA%\Claude\hooks.json
[hooks.json Configuration Reference]
Common Beginner Mistakes
Mistake 1 — Running hooks on every action with no matcher
Without a matcher, your hook fires on every single thing Claude does — reading files, searching, running commands. This slows down every session. Always add a matcher.
Mistake 2 — Using a formatter that modifies files Claude is still working on
If Claude is mid-task and your hook modifies a file, Claude may get confused by the unexpected change. Use Stop hooks for heavy operations — not PostToolUse.
Mistake 3 — Forgetting the {file} placeholder
Your command needs to know which file to act on. Always use {file} as the placeholder — Claude replaces it with the actual filename at runtime.
Mistake 4 — Setting on_error to “stop” for non-critical hooks
If your notification hook fails because you’re offline, you don’t want Claude to stop mid-task. Set on_error: continue for anything that isn’t critical to your workflow.
Mistake 5 — Putting hooks.json in the wrong folder
It must be in .claude/hooks.json inside your project — not the project root, not inside src/. The .claude/ folder is where Claude Code looks.
Quick Reference Cheatsheet
| Task | Hook type | Matcher | Command |
|---|---|---|---|
| Format Python | PostToolUse | `*.py` | `black {file}` |
| Lint JavaScript | PostToolUse | `*.js` | `eslint –fix {file}` |
| Run tests | PostToolUse | `*.test.*` | `jest {file}` |
| Notify on done | Stop | none | `osascript -e ‘…’` |
| Log all edits | PostToolUse | edit_file | `echo ‘{file}’ >> log.txt` |
| Type check TypeScript | PostToolUse | `*.ts` | `tsc –noEmit` |
What to Learn Next
Now that you understand hooks, go deeper with these guides:
- [What Are Claude Code Hooks?] — a shorter plain-English explainer, great to share with teammates
- [The Claude Code Lifecycle Explained] — understand exactly when each hook fires
- [PreToolUse Hooks Explained] — how to intercept and validate before Claude acts
- [PostToolUse Hooks Explained] — the most powerful hook type, in full detail
- [Stop Hooks Explained] — trigger actions when the whole task is done
- [How to Write Your First Hook] — a more detailed walkthrough for your first real hook
- [Hook Matchers Explained] — master file patterns and tool targeting
- [Auto-Format Python Files with a Hook] — step-by-step tutorial
- [Auto-Lint JavaScript with a Hook] — step-by-step tutorial
- [Desktop Notifications When Claude Finishes] — never miss a completed task
- [Run Tests Automatically After Claude Edits] — automate your test suite
- [hooks.json Configuration Reference] — every field, every option
Key Takeaways
- Hooks are automated scripts triggered by Claude Code’s actions — set them up once, they run forever
- Three types: PreToolUse (before), PostToolUse (after), Stop (when done)
- Use matchers to target specific file types or tools — never run hooks on everything
- hooks.json lives in your project’s
.claude/folder - Start simple — one PostToolUse hook for auto-formatting is enough to start