Claude Code can read files, edit code, run commands, execute tests, and work through multi-step development tasks. Claude Code Hooks let you add deterministic automation around those actions.
For example, you can configure Claude Code to:
- format a file after Claude edits it
- inspect a shell command before it runs
- block changes to protected files
- notify you when Claude needs your attention
- load project context when a session starts
- record configuration changes
- validate work before Claude stops
- run an external command or MCP tool after a specific event
The key idea is simple:
Claude decides what to do; hooks let you define what must happen when a specific event occurs.
That distinction makes hooks particularly useful for repetitive tasks, guardrails, validation, and project automation.
This guide explains Claude Code Hooks from the beginning, including events, matchers, configuration, command hooks, structured output, security, troubleshooting, Windows considerations, and practical examples.
What Are Claude Code Hooks?
Claude Code Hooks are user-defined automations that run at specific points in Claude Code's lifecycle.
The simplest mental model is:
Claude Code event
↓
Matcher
↓
Hook condition
↓
Action
↓
Command / script / prompt / agent / MCP tool
↓
Result
Suppose Claude wants to run:
rm -rf ./build
A PreToolUse hook can inspect that operation before it happens.
The workflow becomes:
Claude requests Bash
↓
PreToolUse
↓
Matcher: Bash
↓
Hook checks command
↓
Safe → continue
Risky → deny
This is different from simply putting an instruction inside CLAUDE.md.
CLAUDE.md says:
"Don't delete the production directory."
A hook can enforce a deterministic rule around the relevant operation.
That difference is one of the main reasons hooks are useful.
Claude Code Hooks vs Normal Commands
A normal shell command only runs when something explicitly invokes it.
For example:
npm run lint
does nothing until you execute it.
A hook attaches that command to an event:
Claude edits code
↓
PostToolUse
↓
npm run lint
The command has become event-driven automation.
Claude Code's official documentation describes hooks as a way to create deterministic behavior around lifecycle events rather than relying on the model to remember to perform an action.
That makes hooks particularly useful for rules that should happen consistently.
Why Use Claude Code Hooks?
There are several practical reasons to use them.
Automatic formatting
Problem: Claude edits a TypeScript file and the formatting needs to be fixed afterward.
Hook solution: Use PostToolUse with an Edit|Write matcher and run your formatter.
Result: Formatting becomes part of the workflow rather than another instruction you have to remember.
Security validation
Problem: You don't want Claude to execute certain dangerous commands.
Hook solution: Use PreToolUse and inspect the command before execution.
Result: The hook can return a denial decision for matching operations.
Protected files
Problem: Certain files such as production configuration or secret-related files should not be modified automatically.
Hook solution: Match file-editing tools and run a validation script.
Result: The hook can stop a prohibited operation before it proceeds.
Notifications
Problem: Claude is working on a long task and you don't want to keep watching the terminal.
Hook solution: Use the Notification event to trigger an operating-system notification or external notification workflow.
Result: You can work on something else and return when Claude needs attention.
Session initialization
Problem: Every Claude Code session needs the same project information.
Hook solution: Use SessionStart to load relevant context.
Result: Claude can receive automatically generated context when the session begins.
Configuration auditing
Problem: You want to know when Claude Code configuration changes.
Hook solution: Use ConfigChange.
Result: The hook can record which configuration changed and when.
How Claude Code Hooks Work
A hook normally has several components.
1. Event
The event identifies when the hook fires.
Examples include:
SessionStart UserPromptSubmit PreToolUse PostToolUse Notification Stop SessionEnd PreCompact PostCompact SubagentStart SubagentStop ConfigChange FileChanged
The current Claude Code event system is much larger than the short event lists found in many older tutorials.
The official reference currently documents events covering tool execution, sessions, prompts, subagents, tasks, teams, configuration, files, worktrees, compaction, model switching, MCP elicitation, and other lifecycle operations.
2. Matcher
The matcher determines which event instances should activate a hook.
For tool events:
"matcher": "Bash"
means the hook targets Bash tool calls.
You can also match multiple tools:
"matcher": "Edit|Write"
A matcher can also use regular expressions when the value requires regex syntax.
3. if condition
Current Claude Code also supports an if condition for tool-related events.
This gives you another filtering layer.
For example:
"matcher": "Bash", "if": "Bash(git *)"
Conceptually:
Event ↓ Matcher ↓ Bash? ↓ if condition ↓ git command? ↓ Run hook
This can be useful because the broader matcher can identify a tool while the if rule narrows the operation further.
For example, you might want a hook for Bash commands generally but only run an expensive security check when the command begins with git.
Claude Code Hook Lifecycle
A simplified workflow looks like this:
Session starts
↓
SessionStart
↓
User submits prompt
↓
UserPromptSubmit
↓
Claude calls a tool
↓
PreToolUse
↓
Tool executes
↓
PostToolUse
↓
Claude continues
↓
Stop
↓
SessionEnd
That is only a simplified view.
Modern Claude Code also has events such as:
PostToolUseFailure PostToolBatch PermissionRequest PermissionDenied SubagentStart SubagentStop TaskCreated TaskCompleted TeammateIdle InstructionsLoaded ConfigChange CwdChanged DirectoryAdded FileChanged WorktreeCreate WorktreeRemove PreCompact PostCompact PreModelSwitch PostModelSwitch Elicitation ElicitationResult
The exact behavior differs by event.
For example, some events can block an operation while others exist mainly for logging or side effects.
Claude Code Hook Events
Here are some of the most useful current events for beginners:
| Hook Event | When It Runs | Practical Use |
|---|---|---|
| SessionStart | Session starts/resumes | Load context or environment information |
| UserPromptSubmit | User submits a prompt | Validate or add context |
| PreToolUse | Before a tool executes | Security and validation |
| PermissionRequest | Permission decision is requested | Automate supported permission decisions |
| PostToolUse | Tool succeeds | Format, validate, log |
| PostToolUseFailure | Tool fails | Error logging or recovery |
| PostToolBatch | Parallel tool batch finishes | Batch validation |
| Notification | Claude Code sends notification | Desktop/external alerts |
| SubagentStart | Subagent starts | Track subagent activity |
| SubagentStop | Subagent finishes | Validate/report subagent work |
| Stop | Claude finishes responding | Final checks |
| StopFailure | Turn ends because of API error | Failure handling |
| SessionEnd | Session ends | Cleanup/logging |
| InstructionsLoaded | CLAUDE.md/rules load | Audit instruction loading |
| ConfigChange | Configuration changes | Configuration auditing |
| FileChanged | Watched file changes | File automation |
| PreCompact | Before compaction | Preserve information |
| PostCompact | After compaction | Restore/reinject context |
| PreModelSwitch | Before model switch | Control model changes |
| PostModelSwitch | After model switch | Add model-specific context |
This is not merely a cosmetic expansion of the old hooks system. The newer events allow developers to build much more targeted automation.
Where Are Claude Code Hooks Configured?
Common settings locations include:
~/.claude/settings.json
for user settings,
.claude/settings.json
for project settings,
and:
.claude/settings.local.json
for local project settings.
Claude Code can also obtain hooks through plugins and session-level mechanisms.
The /hooks command provides a read-only browser showing configured hooks and their sources, including:
- User Settings
- Project Settings
- Local Settings
- Plugin Hooks
- Session Hooks
This makes /hooks one of the easiest ways to confirm whether Claude Code actually loaded your configuration.
Your First Claude Code Hook
Start with something harmless.
Do not begin with a complicated security system.
A notification hook is a good example because it produces an obvious result.
Open:
~/.claude/settings.json
and add:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "YOUR_NOTIFICATION_COMMAND"
}
]
}
]
}
}
Replace YOUR_NOTIFICATION_COMMAND with a command appropriate for your operating system.
The important structure is:
hooks ↓ Notification ↓ matcher ↓ command
If your settings file already contains a hooks object, do not replace it.
Add the new event alongside the existing events.
For example:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "YOUR_FORMATTER"
}
]
}
],
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "YOUR_NOTIFICATION_COMMAND"
}
]
}
]
}
}
Verify Your Hook With /hooks
After saving the settings, start Claude Code and run:
/hooks
The /hooks interface is useful for checking configuration.
It can show:
- event
- matcher
- handler type
- source settings file
- command
- prompt
- URL
The menu is read-only. Configuration changes are made in the settings files or through Claude Code.
This is an important troubleshooting step that many beginner tutorials miss.
Instead of immediately assuming the command is broken, first ask:
Did Claude Code actually load the hook?
Hook Input: The Important Part Beginners Often Miss
Command hooks receive event information through standard input as JSON.
For example, a PreToolUse hook for Bash can receive data similar to:
{
"session_id": "abc123",
"cwd": "/home/user/my-project",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
}
That means your script can read the JSON and make decisions based on the actual event.
A Bash script can start with:
#!/bin/bash input=$(cat) echo "$input"
The exact fields depend on the event.
This is more reliable than assuming a fixed set of environment variables contains every piece of information you need.
Hook Output and Exit Codes
This is another area where older tutorials can be misleading.
A command hook can communicate with Claude Code through its exit code and structured stdout/stderr behavior.
The basic concepts are:
Exit 0
Generally means the hook completed successfully.
For standard decision events, Claude Code can also parse structured JSON from stdout.
Exit 2
For events using the standard decision model, exit code 2 represents a blocking error.
The stderr output can be surfaced as the reason.
Other non-zero codes
Behavior depends on the event.
Some events treat errors as non-blocking warnings, while others have special behavior.
Therefore, don't use the simplistic rule:
"Any non-zero exit code always blocks."
That is not correct across every current Claude Code hook event.
Blocking a Dangerous Bash Command
Here is the basic idea of a security hook:
Claude wants to run Bash
↓
PreToolUse
↓
Matcher = Bash
↓
Security script reads stdin JSON
↓
Inspect tool_input.command
↓
Dangerous?
↙ ↘
YES NO
↓ ↓
DENY Continue
A structured decision can look like:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive command blocked by hook"
}
}
The important detail is that the hook is not merely printing:
Blocked
It is returning a structured decision that Claude Code understands.
A Powerful Feature: additionalContext
Hooks can also send useful information back into Claude's context.
For example:
{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "This file is generated. Do not edit it directly."
}
}
Claude Code can inject that information as context at the appropriate point in the conversation.
This creates an interesting workflow:
Tool action
↓
Hook checks result
↓
Hook generates context
↓
Claude receives context
↓
Claude adjusts next action
This is particularly useful when automation needs to inform Claude about something discovered during the workflow.
For example:
- generated files
- validation results
- environment information
- repository state
- test failures
- project-specific restrictions
Example 1: Auto-Format Edited Files
A common hook is:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}
The flow is:
Claude edits file
↓
PostToolUse
↓
Edit or Write?
↓
Extract file path
↓
Prettier
↓
Formatted file
This is much better than running:
npx prettier --write .
after every edit because the hook can target the affected file.
Important
The example assumes:
- jq is installed
- Prettier is available
- the project actually uses Prettier
- the incoming tool input contains the expected file path
If your project uses another formatter, use that instead.
Example 2: Run Tests Without Making Claude Slow
It is tempting to write:
After every edit → run the entire test suite
but that can become expensive.
Imagine Claude edits 15 files.
You could accidentally create:
Edit ↓ Full test suite Edit ↓ Full test suite Edit ↓ Full test suite
A better approach is to consider:
- lightweight validation after edits
- targeted tests
- a final test stage
- asynchronous hooks
- project-specific scripts
Claude Code supports asynchronous command hooks, which can be useful when you want a longer-running operation to continue in the background rather than holding up the normal workflow.
For example:
File changed
↓
PostToolUse
↓
Async test
↓
Claude continues
This is a better design for some expensive checks than forcing every operation to wait.
Example 3: Protect Sensitive Files
Suppose your project contains:
.env production.config.js credentials.json
You could create a PreToolUse hook around file-editing operations.
Conceptually:
Edit/Write
↓
PreToolUse
↓
Check path
↓
Protected?
↙ ↘
Yes No
↓ ↓
Deny Continue
The exact implementation should inspect the actual tool_input received by the hook.
Do not rely on a simple filename check if your security requirement is more complicated.
Example 4: Security Check for Bash and PowerShell
Current Claude Code can target both Bash and PowerShell tool calls.
For example:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|PowerShell",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "YOUR_SECURITY_SCRIPT"
},
{
"type": "command",
"if": "PowerShell(Remove-Item *)",
"command": "YOUR_SECURITY_SCRIPT"
}
]
}
]
}
}
The important concept here is the combination of:
matcher + if
The matcher identifies relevant tools.
The if condition narrows the actual command pattern.
This can reduce unnecessary script execution.
Example 5: Notifications
The current official Claude Code guide provides platform-specific examples for:
- macOS
- Linux
- Windows PowerShell
The Notification event can fire when Claude Code sends notifications such as when Claude needs attention or permission.
A notification hook receives information including:
{
"hook_event_name": "Notification",
"message": "Claude needs your permission",
"title": "Permission needed",
"notification_type": "permission_prompt"
}
This means your notification script can use the actual notification message rather than showing the same hard-coded text every time.
A notification hook is intended for side effects. It cannot block or modify the notification itself.
Example 6: SessionStart and Automatic Context
SessionStart is useful when a project needs dynamic information at the beginning of a session.
For example, a hook could collect:
Current branch Uncommitted files Current issue Environment information Project status
Then return additional context:
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "Current branch: feature/auth\nUncommitted files: src/auth.ts"
}
}
This creates a useful pattern:
New Claude session
↓
SessionStart hook
↓
Collect current project state
↓
Return context
↓
Claude starts with fresh information
This can be particularly useful in repositories where Git state changes frequently.
Example 7: Re-Inject Context After Compaction
Claude Code also provides:
PreCompact PostCompact
events.
These are useful when you build workflows around context compaction.
For example:
Long session
↓
Context gets compacted
↓
PostCompact
↓
Restore useful project information
↓
Claude continues
This is a more advanced use case.
Don't build a complicated compaction workflow until you understand how Claude Code's normal context management behaves.
Example 8: Audit Configuration Changes
The ConfigChange event can monitor configuration changes.
A simple architecture is:
Claude Code configuration changes
↓
ConfigChange
↓
Audit script
↓
Log the change
The event can provide information about the configuration source and, where available, the affected file.
This can be useful for teams that want better visibility into automated-development environments.
Example 9: InstructionsLoaded
A particularly useful newer event is:
InstructionsLoaded
It fires when Claude Code loads instruction files such as:
CLAUDE.md .claude/rules/*.md
This is useful for:
- audit logging
- compliance tracking
- observability
- understanding which instructions were loaded
An important limitation is that InstructionsLoaded does not provide decision control.
In other words, it is useful for observing instruction loading, not for blocking it.
Example 10: SessionEnd
SessionEnd runs when a Claude Code session ends.
Possible uses include:
- logging
- cleanup
- saving session statistics
- recording the end reason
The hook receives a reason such as:
clear resume logout prompt_input_exit other
Unlike PreToolUse, SessionEnd cannot stop the session from ending.
That makes it a good lifecycle-cleanup event rather than a security gate.
Matchers Explained
Matchers deserve special attention because they determine how often your automation fires.
A basic matcher:
"matcher": "Bash"
matches Bash.
Multiple exact tools:
"matcher": "Edit|Write"
can target either tool.
A matcher can also use regular expressions.
For example:
"matcher": "^Notebook"
can match tool names beginning with Notebook.
Current Claude Code matcher behavior also has version-specific details around exact matching, commas, hyphens, and regex interpretation.
For a production configuration, check the current Claude Code version and documentation instead of copying an old matcher example blindly.
Matcher vs if
This distinction is worth understanding.
You can think of it as:
matcher = Which event/tool family? if = Which specific operation inside it?
For example:
"matcher": "Bash", "if": "Bash(git *)"
means:
Only Bash ↓ Only git-related Bash commands ↓ Run hook
This can be more efficient than running your script for every Bash command.
Hooks vs Commands vs MCP
| Feature | Hooks | Slash Commands | MCP |
|---|---|---|---|
| Main purpose | Event-driven automation | User-invoked workflow | External tools/data |
| Trigger | Lifecycle event | User | Claude/tool interaction |
| Deterministic automation | Strong | Limited | Depends on tool |
| Security/guardrails | Strong for supported events | Limited | Depends on implementation |
| External services | Possible | Possible | Core use case |
| Best use case | Validation/automation | Repeatable manual workflow | External capability |
| Beginner difficulty | Medium | Low | Medium |
The three technologies are complementary.
For example:
Slash command
↓
Claude starts workflow
↓
MCP provides external data
↓
Hook validates tool operation
There is no reason to force every workflow into one mechanism.
Hooks vs CLAUDE.md
This is one of the most important distinctions for beginners.
Use CLAUDE.md for instructions
For example:
Use TypeScript strict mode. Follow the existing naming conventions. Run the test suite before considering the task complete.
These are instructions and project context.
Use Hooks for automatic reactions
For example:
Before Bash command → inspect command After Edit → run formatter At session start → load Git state After configuration change → record audit entry
A simple mental model:
CLAUDE.md = "These are the rules." Hooks = "When this happens, automatically do this."
You can combine them.
For example, CLAUDE.md can explain the project's testing policy while a hook performs a deterministic validation step.
Prompt-Based and Agent-Based Hooks
Current Claude Code Hooks are not limited to shell commands.
The official system supports several handler types, including:
command http mcp_tool prompt agent
This matters because not every condition is deterministic.
Command hook
Best for:
Run this exact script.
Examples:
- formatter
- linter
- security check
- logging
Prompt hook
Useful when a Claude model should evaluate a condition rather than a fixed shell rule.
Agent hook
Can use a subagent to investigate a condition using tools such as reading or searching files.
The official documentation currently describes agent hooks as experimental, so they should be treated differently from mature command-based automation.
HTTP hook
Useful when the event needs to call an HTTP endpoint.
MCP tool hook
Allows a hook to invoke an MCP tool directly.
This creates another useful distinction:
Deterministic rule
↓
Command hook
Model-based judgment
↓
Prompt / Agent hook
External service
↓
HTTP hook
External tool
↓
MCP tool hook
Choose the simplest mechanism that solves the problem.
Claude Code Hooks Security
Hooks are powerful because they execute automatically.
That is also the main security concern.
A hook can potentially:
- execute shell commands
- read files
- modify files
- make network requests
- interact with external services
- block operations
- receive sensitive tool input
Therefore, treat hook configuration as executable automation.
Do not blindly copy hooks
A repository may contain:
.claude/settings.json .claude/hooks/
Inspect both.
A small JSON configuration might call a large shell script.
The risk is in what the script actually does.
Avoid secrets
Do not hard-code:
API keys passwords tokens private credentials
into hook files.
Remember that hook input can contain tool arguments, paths, prompts, and other project information.
Be careful with third-party hooks
Before installing a hook from GitHub or a blog:
- Read the configuration.
- Find every referenced script.
- Read those scripts.
- Check network requests.
- Check file writes.
- Check destructive commands.
- Check environment-variable usage.
- Test in a safe environment.
Hooks and Workspace Trust
Workspace trust matters because hooks can execute code automatically.
Treat a project containing hook configuration similarly to a project containing executable scripts.
This is particularly important when cloning unfamiliar repositories.
A repository can contain automation that executes as soon as Claude Code reaches the relevant event.
Do not assume:
"It's only a settings file, so it's safe."
The settings file can launch commands.
Don't Make Hooks Slow
Hooks can dramatically affect the developer experience if they execute expensive operations too often.
Bad design:
Every file edit
↓
Full build
↓
Full test suite
↓
Dependency scan
Better:
File edit ↓ Fast formatter Stop ↓ Final validation Explicit command ↓ Full test suite
Or use asynchronous automation where appropriate.
The goal should be:
Automate repetitive work without turning every Claude action into a waiting period.
Claude Code Hooks on Windows
Windows deserves special attention because many Claude Code hook examples online use Bash.
Claude Code also supports PowerShell-oriented hook commands.
For example:
{
"type": "command",
"command": "powershell.exe",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"${CLAUDE_PROJECT_DIR}/.claude/hooks/check.ps1"
]
}
A PowerShell script can read hook input from standard input:
$inputData = [Console]::In.ReadToEnd() | ConvertFrom-Json $command = $inputData.tool_input.command
This is often cleaner on Windows than trying to force a Bash script into a PowerShell environment.
For cross-platform projects, consider placing the logic in a language such as Node.js or Python and keeping the hook configuration thin.
Common Claude Code Hook Problems
Hook does not run
Symptom
Nothing happens.
Possible causes
- Wrong settings location
- Invalid JSON
- Incorrect event
- Matcher does not match
- Hook not loaded
- Command unavailable
- Script error
Fix
First run:
/hooks
If the hook does not appear there, investigate configuration before investigating the script.
Invalid JSON
Symptom
The configuration appears to have no effect.
Fix
Check:
- commas
- quotes
- brackets
- braces
- escaping
Then verify the hook appears in /hooks.
Matcher is wrong
Symptom
The hook exists but does not trigger.
Fix
Start broad:
"matcher": ""
or an appropriate broad matcher for the event.
Once you confirm the hook fires, narrow it.
if condition is wrong
Symptom
The event matches but the actual handler never executes.
Fix
Temporarily remove the if condition.
If the hook now runs, the filtering rule needs correction.
Command not found
Test the command outside Claude Code first.
For example:
prettier --version
or:
prettier --version
If the command fails manually, fix that environment problem first.
Hook blocks legitimate work
A badly written security hook can be more annoying than the problem it was designed to solve.
Start with:
Log ↓ Observe ↓ Test ↓ Then block
Do not begin with an aggressive blocking policy.
Debugging Claude Code Hooks
One practical recommendation from the community sources you provided is to use Claude Code's debugging facilities while developing a hook.
For example:
claude --debug
can help expose hook-related diagnostics.
The official documentation also provides hook debugging guidance.
A useful debugging process is:
1. Check /hooks
↓
2. Confirm event
↓
3. Confirm matcher
↓
4. Test command manually
↓
5. Run with debug logging
↓
6. Inspect stdin JSON
↓
7. Inspect exit code
↓
8. Inspect structured output
This is much faster than randomly changing JSON until something works.
Testing a Hook Without Starting a Full Interactive Workflow
A useful advanced practice is to separate the hook's logic from Claude Code itself.
Suppose your hook script expects:
{
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
}
You can save a representative fixture and pipe it into your script.
Conceptually:
cat fixture.json | ./check-command.sh
This lets you test:
Input ↓ Script ↓ Exit code ↓ JSON output
without repeatedly performing the real Claude Code action.
This is especially valuable for security hooks.
When NOT to Use a Hook
Hooks are not automatically the right solution.
Don't use a hook when:
The task is genuinely manual
If you only need something once, a normal command may be simpler.
The action requires nuanced judgment
A deterministic shell rule may be the wrong tool when the decision requires understanding the entire codebase.
A prompt or agent-based workflow may be more appropriate where supported.
The command is extremely expensive
Running a ten-minute process after every small edit will quickly become frustrating.
The rule belongs in documentation
If you simply need to explain a coding convention, use CLAUDE.md.
The workflow is explicitly user-triggered
A slash command may be a better fit when the developer should decide exactly when it runs.
Best Claude Code Hooks for Beginners
Rather than calling one hook "the best," categorize them by complexity.
Good first hooks
Start with:
- simple notifications
- lightweight formatting
- basic logging
- session context
- simple validation
Useful project hooks
Next consider:
- protected file checks
- security validation
- targeted testing
- configuration auditing
- context injection
Advanced hooks
After understanding the basics:
- asynchronous hooks
- prompt-based hooks
- agent-based hooks
- HTTP hooks
- MCP tool hooks
- model-switch hooks
- subagent automation
- worktree automation
Avoid initially
Don't start with:
- dozens of hooks
- broad matchers everywhere
- expensive commands after every tool call
- destructive automation
- third-party hooks you have not inspected
Claude Code Hooks Best Practices
Keep hooks focused
One hook should ideally have one clear responsibility.
Use narrow matchers
The narrower the trigger, the fewer unnecessary executions you get.
Use if for additional filtering
When a tool event is broad but only certain commands matter, an if condition can reduce unnecessary handler execution.
Put complex logic in scripts
Instead of:
settings.json = huge shell command
prefer:
settings.json
↓
small hook declaration
↓
project script
Return structured output when you need decisions
If Claude Code needs to understand a hook decision, use the documented JSON output structure rather than relying only on human-readable text.
Test before blocking
First observe.
Then validate.
Then enforce.
Think about operating systems
A Bash hook may work perfectly on Linux and fail on Windows.
Keep performance in mind
A hook that executes every few seconds needs to be lightweight.
Document project hooks
If your project commits:
.claude/settings.json
tell contributors what the hooks do and why they exist.
Review third-party automation
Treat hooks as executable code, not harmless configuration.
Claude Code Hooks vs Git Hooks
These are not the same thing.
Git hook
Runs because of a Git lifecycle event.
Examples:
pre-commit pre-push post-merge
Claude Code hook
Runs because of a Claude Code lifecycle event.
Examples:
PreToolUse PostToolUse SessionStart Stop Notification
You can use both.
For example:
Claude edits code
↓
Claude Code hook
↓
Run formatter
↓
Git commit
↓
Git pre-commit hook
↓
Run final checks
They operate at different layers.
Claude Code Hooks FAQ
What are Claude Code Hooks?
Claude Code Hooks are event-driven automations that execute at defined points in Claude Code's lifecycle.
Where are Claude Code Hooks configured?
Common locations include user settings, project settings, and local project settings such as:
~/.claude/settings.json .claude/settings.json .claude/settings.local.json
Plugins and session-level hooks can also contribute hooks.
What is PreToolUse?
PreToolUse fires before a tool executes. It is useful for validation, security checks, and supported decision control.
What is PostToolUse?
PostToolUse fires after a tool succeeds. It is useful for formatting, logging, and post-operation validation.
Can Claude Code Hooks block commands?
Some events support blocking. PreToolUse is one of the main examples.
The exact behavior depends on the event.
Can Hooks modify tool input?
Supported PreToolUse workflows can return structured output that modifies supported tool input through the documented updatedInput mechanism.
Can Hooks run shell commands?
Yes. Command hooks can execute shell commands or project scripts.
Can Hooks use MCP?
Yes. Current Claude Code supports MCP tool hooks.
One important lifecycle detail: MCP tool hooks require the session's MCP servers to be available. For example, MCP hooks on the initial SessionStart can be skipped because the MCP client context is not yet available.
Can Hooks run on Windows?
Yes. Windows-specific PowerShell commands and scripts are supported.
What does /hooks do?
/hooks opens a read-only browser showing configured hooks, matchers, handler types, and their sources.
Why isn't my Hook running?
Check:
- settings location
- JSON validity
- /hooks
- event name
- matcher
- if condition
- command availability
- script permissions
- debug output
Are Claude Code Hooks safe?
They can be useful security controls, but they are executable automation and should be treated accordingly.
Review third-party hooks, avoid secrets, test blocking logic carefully, and don't run scripts you do not understand.
Final Takeaway
If you are new to Claude Code Hooks, start small.
A good first workflow is:
Claude edits a file
↓
PostToolUse
↓
Run formatter
After that, try a notification hook or a lightweight validation hook.
Once you understand matchers, stdin JSON, exit codes, and structured output, you can move into more advanced automation such as:
PreToolUse ↓ Security validation ↓ Allow / deny SessionStart ↓ Project state ↓ Additional context PostCompact ↓ Restore useful context ConfigChange ↓ Audit SubagentStop ↓ Validate subagent work
The goal is not to make every Claude Code action automated.
The goal is to identify repetitive, deterministic, or safety-sensitive tasks and move them from "Claude should remember to do this" to "the workflow automatically does this."
That is where Claude Code Hooks become genuinely useful.