Claude Code Hooks Tutorial: 3 Practical Examples
A Claude Code hooks tutorial: what hooks are, the main events, where to configure them, the JSON format and three working examples, plus safety notes.
Josh Pocock5 min read
AI Coding
Claude Code hooks
Stride AI Academy
Claude Code hooks are shell commands that run automatically at set points, like after Claude edits a file. You add them to a hooks block in a settings file. Below: the format, the main events and three working examples (auto-formatting, blocking .env edits, notifications).
All configuration below matches Claude Code's hooks guide, checked October 2026. The hooks reference has the full schemas.
What hooks are for
Instructions in CLAUDE.md are requests. Claude usually follows them, but it's still a model deciding. Hooks are deterministic: the command runs every time the event happens. Use them for things that must always happen, or must never happen:
- Format every file Claude edits
- Block edits to secrets or lock files
- Get a notification when Claude is waiting for you
- Log every shell command Claude runs
- Add context back after the conversation is compacted
Where hooks are configured
| File | Scope | Shared? |
|---|---|---|
~/.claude/settings.json |
All your projects | No, local to your machine |
.claude/settings.json |
One project | Yes, commit it to the repo |
.claude/settings.local.json |
One project | No, for your own settings |
Hooks can also come from managed policy settings, plugins, and skill or subagent frontmatter. Type /hooks in Claude Code to see every configured hook grouped by event.
The main events
There are many hook events. These are the ones you'll use most:
| Event | When it fires |
|---|---|
SessionStart |
A session begins or resumes |
UserPromptSubmit |
You submit a prompt, before Claude processes it |
PreToolUse |
Before a tool call runs. Can block it |
PostToolUse |
After a tool call succeeds |
Notification |
Claude Code sends a notification, such as waiting for input |
Stop |
Claude finishes responding |
PreCompact |
Before context compaction |
SessionEnd |
A session ends |
The JSON format
Every hook config follows the same shape: an event name, a list of matcher groups, and the commands to run.
{
"hooks": {
"EventName": [
{
"matcher": "ToolName|OtherTool",
"hooks": [
{ "type": "command", "command": "your-command-here" }
]
}
]
}
}
matcherfilters when the hook fires. For tool events it matches tool names, such asEdit|WriteorBash. An empty string matches everything.commandis the shell command to run. Claude Code passes event details as JSON on standard input, includingtool_nameandtool_inputfor tool events.
If your settings file already has a hooks key, add new events as siblings inside it rather than replacing the whole object.
Exit codes
- Exit 0: no objection. For
PreToolUse, the normal permission flow still applies, so exit 0 doesn't approve anything on its own. - Exit 2: block the action. Whatever you write to standard error is passed back as the reason, so Claude can adjust.
- Other codes: usually a non-blocking error, and the action continues.
For finer control, a hook can exit 0 and print JSON instead. The reference covers those fields.
Example 1: Auto-format after every edit
Run Prettier on each file Claude edits. Add to .claude/settings.json in your project:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}
The command reads the edited file path from the hook input with jq and passes it to Prettier. You need jq installed. To test, ask Claude to add a line with single-quoted strings to a JavaScript file. With Prettier's defaults, the hook rewrites them to double quotes. A successful hook shows nothing in the conversation, so check the file.
Example 2: Block edits to .env and other protected files
Save this script as .claude/hooks/protect-files.sh:
#!/bin/bash
# protect-files.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
# Normalize Windows backslash separators so the patterns below match
FILE_PATH="${FILE_PATH//\\//}"
PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/")
for pattern in "${PROTECTED_PATTERNS[@]}"; do
if [[ "$FILE_PATH" == *"$pattern"* ]]; then
echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
exit 2
fi
done
exit 0
On macOS and Linux, make it executable with chmod +x .claude/hooks/protect-files.sh. Then register it as a PreToolUse hook in .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
}
]
}
]
}
}
Test it by asking Claude to add a comment to your .env file. The edit is blocked, and Claude sees the "Blocked:" message as feedback.
This matters for client work: it stops an agent from rewriting credentials or lock files by accident, even if it decides that's the fix.
Example 3: Get notified when Claude needs you
Add a Notification hook to ~/.claude/settings.json so it applies everywhere. On macOS:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
}
]
}
]
}
}
On Linux, use notify-send 'Claude Code' 'Claude Code needs your attention' as the command. On Windows, the docs give a PowerShell command that opens a message box. The hooks guide has it under "Get notified when Claude needs input."
The empty matcher fires on every notification type. To fire only when Claude needs a permission decision, set the matcher to permission_prompt. For "finished and waiting," use idle_prompt.
Safety notes
- Hooks run with your permissions. A hook can do anything your user account can do. Read every hook before you add it, especially from a repo you didn't write.
- Project hooks are shared. Hooks in
.claude/settings.jsonapply to everyone who clones the repo. Review changes to that file like code. - Keep matchers narrow. A hook on every tool call slows everything down and widens what can go wrong.
- Quote paths. Use
"$CLAUDE_PROJECT_DIR"when calling scripts, as in Example 2, so paths with spaces work. - Turning them off. Set
"disableAllHooks": truein a settings file to disable hooks. Managed settings can still enforce their own hooks.
Learn to build with Claude Code
The Stride AI Coding course teaches Claude Code and Codex for building custom tools for clients, written for non-programmers, with weekly live calls. The free guide library has more Claude Code setups, and our guide on how to create Claude Code skills covers the other main way to customize it.
FAQ
What are the most useful hooks in Claude Code?
For most projects: a PostToolUse hook that formats edited files, a PreToolUse hook that blocks edits to secrets and lock files, and a Notification hook that alerts you when Claude needs input. Add logging or context hooks as your needs grow.
Does Claude Code support hooks?
Yes. Hooks are configured in a hooks block in Claude Code's settings files and fire on events such as PreToolUse, PostToolUse, Notification and Stop. Type /hooks in Claude Code to browse the ones you have.
Can a hook stop Claude from doing something?
Yes. A PreToolUse hook that exits with code 2 blocks the tool call, and its error message is passed back to Claude as the reason. Some events can't be blocked, so check the reference for the event you're using.
Can I ask Claude to write a hook for me?
Yes. Describe what you want, and Claude Code can write the hook config and any script. Review the result before saving it, since hooks run shell commands automatically.