
Tired of reminding Claude to format your code, send notifications, or follow project rules? Claude Code's hook system lets you build event-driven automations that fire at key moments — when files change, commands run, or Claude needs input — without relying on the LLM to remember.
Claude keeps forgetting to format your code. You've asked it three times to run Prettier after editing files, but it only remembers half the time. Sound familiar?
There's a better way than hoping your AI assistant follows instructions consistently.
Relying on LLMs for repetitive, rule-based tasks is like hiring a brilliant consultant to file your paperwork — technically possible, but wildly inefficient. Claude Code's hook system flips this dynamic by letting you build deterministic automations that fire at specific moments in Claude's lifecycle.
Instead of prompting "remember to format the code," you write a hook that automatically runs Prettier every time Claude edits a file. Instead of checking if Claude finished a task, you get a desktop notification the moment it needs your input.
Hooks provide deterministic control over Claude Code's behavior, ensuring certain actions always happen rather than relying on the LLM to choose to run them.
This isn't just about convenience — it's about reliability. Critical workflows like code formatting, file validation, and security checks shouldn't depend on an AI's memory. They should be automatic, consistent, and bulletproof.
Hooks are user-defined shell commands that execute automatically when specific events occur in Claude's workflow. Think of them as event listeners for your AI coding assistant.
The system supports multiple trigger points:
Each hook can run any shell command, from simple notifications to complex validation scripts. You can filter when hooks fire using matchers (think of them as conditional logic), and you can store hook configurations at the user, project, or workspace level.
For decisions that require judgment rather than deterministic rules, you can also use prompt-based hooks or agent-based hooks that use a Claude model to evaluate conditions.
Most automation scenarios use command hooks for their speed and reliability.
Let's build a notification system so you never have to babysit Claude's terminal again. When Claude finishes a task or needs permission, you'll get an instant desktop alert.
The fastest approach is Claude Code's built-in configuration interface:
/hooks in the Claude Code CLINotification from the event list* to catch all notification types+ Add new hook… and enter the platform-specific command belowUser settings to apply across all projectsmacOS (using AppleScript):
osascript -e 'display notification "Claude Code needs your attention" with title "Claude Code"'
Linux (using notify-send):
notify-send 'Claude Code' 'Claude Code needs your attention'
Windows (using PowerShell):
powershell.exe -Command "[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.MessageBox]::Show('Claude Code needs your attention', 'Claude Code')"
Alternatively, add this configuration directly to ~/.claude/settings.json:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "notify-send 'Claude Code' 'Claude Code needs your attention'"
}
]
}
]
}
}
Now when you ask Claude to perform a task that requires permission, switch to another application — you'll receive an immediate notification when it's ready for your input.
Stop reminding Claude to format code. This hook automatically runs Prettier on every file Claude modifies:
{
"hooks": {
"PostToolUse": [
{
"matcher": "str_replace_editor",
"hooks": [
{
"type": "command",
"command": "npx prettier --write {file_path}"
}
]
}
]
}
}
The PostToolUse event fires after Claude uses any tool. The matcher str_replace_editor filters it to only file editing operations. The {file_path} variable gets automatically populated with the edited file's path.
Prevent Claude from accidentally modifying critical configuration files:
{
"hooks": {
"PreToolUse": [
{
"matcher": "str_replace_editor",
"hooks": [
{
"type": "command",
"command": "if [[ '{file_path}' == *'.env'* ]]; then echo 'ERROR: Cannot edit .env files' && exit 1; fi"
}
]
}
]
}
}
This uses PreToolUse to intercept file operations before they execute. If the file path contains .env, the hook exits with an error code, blocking the edit.
Hooks that exit with non-zero status codes will block the triggering action and display an error message to Claude.
When Claude's conversation gets too long, it automatically compacts older messages to save context space. Use hooks to re-inject critical information:
{
"hooks": {
"PostCompaction": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "cat project-guidelines.md"
}
]
}
]
}
}
This hook runs after context compaction and outputs your project guidelines, ensuring Claude always has access to key project information even after conversation history gets compressed.
Automatically commit Claude's changes with descriptive messages:
{
"hooks": {
"PostToolUse": [
{
"matcher": "str_replace_editor",
"hooks": [
{
"type": "command",
"command": "git add {file_path} && git commit -m 'Claude Code: Updated {file_path}'"
}
]
}
]
}
}
Matchers determine when hooks fire. They work differently depending on the event type:
str_replace_editor, bash, etc.)permission_prompt, idle_prompt)""): Fires on all events of that type"*"): Same as empty, but more explicitHooks support dynamic variables that get populated at runtime:
{file_path}: Path to the file being operated on{tool_name}: Name of the tool being used{command}: Shell command being executed{notification_type}: Type of notification being sentHook configurations follow a precedence order:
.claude/settings.json in project root (highest priority).claude/settings.json in workspace root~/.claude/settings.json (lowest priority)This lets you define global defaults in user settings while overriding specific behaviors per project.
Hooks run synchronously by default, blocking Claude until they complete. For long-running operations, you can make hooks asynchronous or add timeout handling:
# Run in background (async)
your-long-command &
# Add timeout
timeout 30s your-command
Failed hooks (non-zero exit codes) will block the triggering action and show error messages to Claude, giving you a safety mechanism for validation hooks.
Hooks transform Claude Code from a helpful assistant that sometimes forgets your preferences into a reliable automation platform that enforces your workflows consistently. Instead of repeatedly prompting Claude to format code, validate files, or send notifications, you define these behaviors once and let the system handle them automatically. The result is faster development cycles, fewer mistakes, and the confidence that critical tasks always happen — regardless of what Claude remembers or forgets.
Rate this tutorial