Hooks: Automating Your Workflow
Hooks let you run custom code automatically when specific events happen in Claude Code. They are the key to automating repetitive tasks, enforcing project rules, and building sophisticated workflows that run without manual intervention.
In this lesson, you will learn how hooks work, how to configure them, and how to use them to automate your development process.
What You Will Learn
- What hooks are and when they trigger
- How to configure hooks in settings.json
- The different hook event types
- Practical examples of useful hooks
- How to use hooks for quality gates and automation
- Debugging and troubleshooting hooks
What Are Hooks?
Hooks are scripts or commands that Claude Code runs automatically at specific points during its workflow. They are defined in your settings.json file and execute without Claude Code making a decision about whether to run them, which makes them fundamentally different from instructions in CLAUDE.md.
Think of hooks like git hooks (pre-commit, post-commit, etc.), but for Claude Code's workflow. They let you:
- Run linters or formatters automatically after Claude Code edits a file
- Validate changes before they are committed
- Send notifications when certain actions complete
- Enforce project rules that cannot be expressed in CLAUDE.md
- Chain multiple tools together in automated workflows
Hook Event Types
Claude Code supports hooks that trigger at different points:
PreToolUse
Runs before Claude Code uses a tool (like editing a file or running a command). Use this to:
- Validate that an action should proceed
- Log what Claude Code is about to do
- Block certain actions under specific conditions
PostToolUse
Runs after Claude Code uses a tool. Use this to:
- Run formatters after file edits
- Validate the results of a command
- Trigger follow-up actions
Notification
Runs when Claude Code wants to notify you about something. Use this to:
- Send alerts to external systems (Slack, email)
- Log activity to a file
- Trigger desktop notifications
Stop
Runs when Claude Code finishes a task and is about to return control to you. Use this to:
- Run final validation checks
- Generate summaries of what changed
- Clean up temporary resources
Configuring Hooks
Hooks are configured in your settings.json file. You can have project-level hooks (.claude/settings.json) or user-level hooks (~/.claude/settings.json).
Basic Hook Structure
\{
"hooks": \{
"PostToolUse": [
\{
"matcher": "Edit|Write",
"command": "npx prettier --write $FILE_PATH"
\}
]
\}
\}
This hook runs Prettier on any file that Claude Code edits or creates. The matcher field specifies which tools trigger the hook (using a pipe | for multiple tools), and command is what runs.
Hook Configuration Fields
Each hook entry has these fields:
- matcher: A pattern that matches tool names. Examples:
"Edit","Write","Edit|Write","Bash". Optional for Notification and Stop hooks. - command: The shell command to execute. Supports environment variables.
- timeout (optional): Maximum time in milliseconds before the hook is killed. Default varies by hook type.
Environment Variables in Hooks
Hooks receive context through environment variables:
| Variable | Description |
|---|---|
$TOOL_NAME | The name of the tool that triggered the hook |
$FILE_PATH | The path of the file being edited (for Edit/Write tools) |
$TOOL_INPUT | JSON string of the tool's input parameters |
$TOOL_OUTPUT | JSON string of the tool's result (PostToolUse only) |
$SESSION_ID | The current session identifier |
Practical Hook Examples
Auto-Format After Edits
Run your formatter automatically whenever Claude Code modifies a file:
\{
"hooks": \{
"PostToolUse": [
\{
"matcher": "Edit|Write",
"command": "npx prettier --write $FILE_PATH"
\}
]
\}
\}
This ensures all code Claude Code writes matches your project's formatting rules, even if Claude Code does not perfectly follow your Prettier configuration.
Lint After Every Edit
Run the linter on changed files to catch issues immediately:
\{
"hooks": \{
"PostToolUse": [
\{
"matcher": "Edit|Write",
"command": "npx eslint --fix $FILE_PATH"
\}
]
\}
\}
Block Modifications to Protected Files
Prevent Claude Code from editing specific files:
\{
"hooks": \{
"PreToolUse": [
\{
"matcher": "Edit|Write",
"command": "echo $FILE_PATH | grep -q 'generated\\|migrations' && echo 'BLOCKED: Cannot modify generated or migration files' && exit 1 || exit 0"
\}
]
\}
\}
If the hook exits with a non-zero code, the tool action is blocked.
Log All Shell Commands
Keep an audit trail of every command Claude Code runs:
\{
"hooks": \{
"PreToolUse": [
\{
"matcher": "Bash",
"command": "echo \"$(date): $TOOL_INPUT\" >> .claude/command-log.txt"
\}
]
\}
\}
Run Tests After Changes
Automatically run related tests after Claude Code modifies source files:
\{
"hooks": \{
"PostToolUse": [
\{
"matcher": "Edit|Write",
"command": "echo $FILE_PATH | grep -q '\\.test\\.' || npm test -- --findRelatedTests $FILE_PATH 2>/dev/null || true"
\}
]
\}
\}
This runs tests related to the changed file (skipping test files themselves to avoid recursion).
Desktop Notification When Done
Get a system notification when Claude Code completes a long task:
\{
"hooks": \{
"Stop": [
\{
"command": "osascript -e 'display notification \"Claude Code finished the task\" with title \"Claude Code\"'"
\}
]
\}
\}
This macOS example uses AppleScript. On Linux, you could use notify-send instead.
Combining Hooks for Quality Gates
You can chain multiple hooks together to create comprehensive quality gates:
\{
"hooks": \{
"PostToolUse": [
\{
"matcher": "Edit|Write",
"command": "npx prettier --write $FILE_PATH"
\},
\{
"matcher": "Edit|Write",
"command": "npx eslint --fix $FILE_PATH"
\}
],
"Stop": [
\{
"command": "npm run typecheck"
\}
]
\}
\}
This configuration:
- Formats every file Claude Code edits
- Lints every file Claude Code edits
- Runs the type checker when Claude Code finishes a task
Hook Best Practices
Keep hooks fast. Hooks run synchronously, so slow hooks delay Claude Code's workflow. If a hook takes more than a second, consider running it asynchronously or only on specific file types.
Handle errors gracefully. A hook that crashes can disrupt Claude Code's workflow. Use || true at the end of commands that might fail if you want the hook to be non-blocking.
Use matchers to be specific. Instead of running a hook on every tool use, match only the tools you care about. This reduces unnecessary processing.
Test hooks independently. Before adding a hook to your configuration, test the command manually in your terminal to make sure it works as expected.
Document your hooks. Add a comment in your CLAUDE.md explaining what hooks are configured and why, so team members understand the automated behaviors.
Debugging Hooks
Hook is not running: Check that the matcher matches the exact tool name. Common tool names are Edit, Write, Bash, Read.
Hook command fails: Run the command manually in your terminal, substituting the environment variables with real values, to see the error.
Hook blocks an action unexpectedly: Check PreToolUse hooks for exit codes. A non-zero exit code blocks the action.
Hook runs too slowly: Add a timeout to prevent slow hooks from blocking the workflow, or make the hook asynchronous by backgrounding the command.
Key Takeaways
- Hooks are scripts that run automatically at specific points in Claude Code's workflow: PreToolUse, PostToolUse, Notification, and Stop
- Configure hooks in
.claude/settings.json(project-level) or~/.claude/settings.json(user-level) with matcher, command, and optional timeout fields - Common uses include auto-formatting after edits, running linters, blocking modifications to protected files, and sending notifications
- Hooks receive context through environment variables like $FILE_PATH, $TOOL_NAME, and $TOOL_INPUT
- PreToolUse hooks that exit with non-zero codes block the action, making them useful for enforcing rules
- Keep hooks fast and handle errors gracefully to avoid disrupting Claude Code's workflow
- Combine multiple hooks to create comprehensive quality gates that ensure code quality automatically

