Hooks and Automation
Hooks and Automation
Hooks are one of Claude Code's most powerful automation features. They let you run shell commands automatically in response to specific events during a Claude Code session. Want to auto-format every file Claude Code edits? Run linting after every code change? Send yourself a notification when a long task finishes? Hooks make all of this possible without any manual intervention.
What You Will Learn
- What hooks are and how they work
- The four hook types and when each fires
- How to configure hooks in your settings
- Practical examples for common automation needs
- Hook matchers for filtering which tools trigger hooks
- The hook response format for controlling Claude Code's behavior
- Real-world use cases for teams and individuals
What Are Hooks?
Hooks are shell commands that Claude Code executes automatically when specific events occur. They run outside of Claude Code's AI decision-making -- they are deterministic, predictable, and always execute when their trigger conditions are met.
Think of hooks like git hooks (pre-commit, post-merge, etc.), but for Claude Code events. Just as a git pre-commit hook can run a linter before every commit, a Claude Code hook can run a formatter after every file edit.
Why Use Hooks?
Without hooks, you would need to manually remind Claude Code to format files, run linters, or follow specific procedures after every change. Hooks automate these steps so they happen consistently every time, regardless of what you or Claude Code are focused on.
Hook Types
Claude Code supports four types of hooks, each triggered by a different event.
PreToolUse
Runs before Claude Code executes a tool. This gives you a chance to validate, modify, or block the action before it happens.
Common uses:
- Validate that file edits follow conventions before they are applied
- Block writes to protected files
- Log what Claude Code is about to do
PostToolUse
Runs after Claude Code executes a tool. This is the most commonly used hook type because it lets you react to changes that have already been made.
Common uses:
- Auto-format files after they are edited
- Run linting after code changes
- Update related files when a specific file changes
Notification
Runs when Claude Code wants to notify you about something. This is useful for integrating with notification systems.
Common uses:
- Send a desktop notification when Claude Code needs your attention
- Post to a Slack channel when a task reaches a certain point
- Play a sound when Claude Code is waiting for input
Stop
Runs when Claude Code finishes a response or completes a task. This hook fires at the end of each turn.
Common uses:
- Send a notification that a long-running task is complete
- Run a final validation check after Claude Code finishes working
- Log session activity for audit purposes
Configuring Hooks
Hooks are configured in your settings file. You can set them at the project level in .claude/settings.json or at the user level in ~/.claude/settings.json.
Basic Structure
Here is the general structure of a hooks configuration:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"command": "npx prettier --write \"$CLAUDE_FILE_PATH\""
}
]
}
}
Each hook entry has:
- matcher (optional) -- A pattern that filters which tools trigger the hook. If omitted, the hook runs for every tool invocation of that type.
- command -- The shell command to execute when the hook fires.
Environment Variables
When a hook runs, Claude Code makes several environment variables available to your command:
$CLAUDE_FILE_PATH-- The file path involved in the tool call (for file-related tools)$CLAUDE_TOOL_NAME-- The name of the tool that was used$CLAUDE_TOOL_INPUT-- JSON string of the tool's input parameters$CLAUDE_TOOL_OUTPUT-- JSON string of the tool's output (PostToolUse only)
These variables let your hook commands be dynamic and context-aware.
Example: Auto-Format Code After Every Edit
This is the most popular use case for hooks. Every time Claude Code writes or edits a file, Prettier runs automatically to ensure consistent formatting.
Add this to .claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"command": "npx prettier --write \"$CLAUDE_FILE_PATH\" 2>/dev/null || true"
}
]
}
}
Let us break this down:
- Hook type:
PostToolUse-- runs after a tool is used - Matcher:
Write|Edit-- only triggers when Claude Code uses its Write or Edit tools (i.e., modifies files) - Command: Runs Prettier on the edited file. The
2>/dev/null || trueensures the hook does not fail if Prettier encounters a file type it cannot format
Now every file Claude Code touches will be automatically formatted. You never need to ask Claude Code to "make sure it is formatted" or run Prettier manually.
Example: Run Linting After Code Changes
You can run ESLint after every file edit to catch issues immediately:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"command": "npx eslint --fix \"$CLAUDE_FILE_PATH\" 2>/dev/null || true"
}
]
}
}
This runs ESLint with the --fix flag, which automatically corrects fixable issues. If ESLint finds problems it cannot auto-fix, the hook still succeeds (because of || true), and Claude Code can see the linting output.
Combining Formatting and Linting
You can have multiple hooks for the same event type:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"command": "npx eslint --fix \"$CLAUDE_FILE_PATH\" 2>/dev/null || true"
},
{
"matcher": "Write|Edit",
"command": "npx prettier --write \"$CLAUDE_FILE_PATH\" 2>/dev/null || true"
}
]
}
}
Hooks run in the order they are defined. Here, ESLint runs first (fixing code issues), then Prettier runs (fixing formatting). This order matters because ESLint fixes can introduce formatting that Prettier then cleans up.
Example: Notification When a Long Task Completes
If you ask Claude Code to do something that takes a while (like refactoring a large module), you might switch to another window. A Stop hook can notify you when Claude Code is done:
macOS Notification
{
"hooks": {
"Stop": [
{
"command": "osascript -e 'display notification \"Claude Code has finished the task\" with title \"Claude Code\"'"
}
]
}
}
Linux Notification
{
"hooks": {
"Stop": [
{
"command": "notify-send 'Claude Code' 'Task completed'"
}
]
}
}
Play a Sound (macOS)
{
"hooks": {
"Stop": [
{
"command": "afplay /System/Library/Sounds/Glass.aiff"
}
]
}
}
These hooks fire every time Claude Code finishes a response, so you always know when it is your turn.
Hook Matchers: Filtering Which Tools Trigger Hooks
Matchers let you control exactly which tool invocations trigger your hooks. Without a matcher, a hook runs for every tool call of that type.
Tool Name Matching
The matcher string is compared against the tool name. You can use the pipe character to match multiple tools:
{
"matcher": "Write",
"command": "echo 'A file was written'"
}
{
"matcher": "Write|Edit",
"command": "echo 'A file was modified'"
}
{
"matcher": "Bash",
"command": "echo 'A shell command was executed'"
}
Common Tool Names
Here are the tool names you will use most often in matchers:
Write-- Claude Code creates or overwrites a fileEdit-- Claude Code makes targeted edits to an existing fileBash-- Claude Code runs a shell commandRead-- Claude Code reads a fileGlob-- Claude Code searches for files by patternGrep-- Claude Code searches file contents
No Matcher (Catch-All)
If you omit the matcher, the hook runs for every tool invocation:
{
"hooks": {
"PostToolUse": [
{
"command": "echo \"Tool used: $CLAUDE_TOOL_NAME\" >> /tmp/claude-audit.log"
}
]
}
}
This logs every tool Claude Code uses to a file, creating an audit trail.
The Hook Response Format
Hooks can do more than just run commands. Depending on the hook type, they can influence Claude Code's behavior.
PreToolUse: Blocking Actions
A PreToolUse hook can prevent Claude Code from executing a tool. If the hook command exits with a non-zero status code, the tool invocation is blocked.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"command": "echo \"$CLAUDE_FILE_PATH\" | grep -v 'node_modules' && echo \"$CLAUDE_FILE_PATH\" | grep -v '.env'"
}
]
}
}
This hook blocks writes to any file in node_modules or any .env file. If the grep fails (meaning the path contains the blocked pattern), the command exits with a non-zero code and the write is prevented.
PostToolUse: Informing Claude Code
The output of a PostToolUse hook is fed back to Claude Code. This means if your hook produces output (like linting errors), Claude Code can see and react to it.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"command": "npx eslint \"$CLAUDE_FILE_PATH\" 2>&1 || true"
}
]
}
}
If ESLint finds issues, its output is shown to Claude Code, which can then fix the problems in its next action. This creates a feedback loop: Claude Code edits a file, the hook runs the linter, Claude Code sees the linting errors, and Claude Code fixes them.
Stop: Final Actions
Stop hooks run after Claude Code completes its response. Their output does not affect Claude Code's behavior since the response is already complete. Use them for notifications, logging, and cleanup.
Practical Use Cases
Enforcing Team Conventions
Use PreToolUse hooks to enforce rules that your team has agreed on:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"command": "echo \"$CLAUDE_FILE_PATH\" | grep -qv '\\.test\\.' || echo 'BLOCKED: Do not modify test files without explicit permission'"
}
]
}
}
This warns when Claude Code tries to modify test files, encouraging it to fix the implementation code instead.
Audit Logging
Track everything Claude Code does for compliance or review:
{
"hooks": {
"PostToolUse": [
{
"command": "echo \"$(date -u +%Y-%m-%dT%H:%M:%SZ) | Tool: $CLAUDE_TOOL_NAME | File: $CLAUDE_FILE_PATH\" >> .claude/audit.log"
}
]
}
}
This creates a timestamped log of every tool invocation, which is useful for teams that need to track AI-assisted changes.
Auto-Running Tests After Changes
Run relevant tests automatically after Claude Code modifies source code:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"command": "if echo \"$CLAUDE_FILE_PATH\" | grep -q 'src/'; then npm test -- --findRelatedTests \"$CLAUDE_FILE_PATH\" 2>&1 | tail -5 || true; fi"
}
]
}
}
This runs Jest's --findRelatedTests flag to execute only the tests related to the changed file. The output is truncated to the last 5 lines to keep things concise.
Type Checking After Edits
Run the TypeScript compiler after every file change to catch type errors immediately:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"command": "if echo \"$CLAUDE_FILE_PATH\" | grep -qE '\\.(ts|tsx)$'; then npx tsc --noEmit 2>&1 | head -20 || true; fi"
}
]
}
}
This only runs on TypeScript files and limits output to the first 20 lines of errors.
Combining Hooks with CLAUDE.md
Hooks and CLAUDE.md work together to create a comprehensive project setup. Your CLAUDE.md tells Claude Code about your project, and hooks enforce automated workflows.
Here is an example of how they complement each other:
CLAUDE.md:
## Conventions
- All code must be formatted with Prettier before commit
- ESLint must pass with zero errors
- TypeScript strict mode is enabled -- no `any` types
- Test files live next to source files with `.test.ts` suffix
.claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"command": "npx prettier --write \"$CLAUDE_FILE_PATH\" 2>/dev/null || true"
},
{
"matcher": "Write|Edit",
"command": "npx eslint --fix \"$CLAUDE_FILE_PATH\" 2>&1 || true"
}
],
"Stop": [
{
"command": "osascript -e 'display notification \"Claude Code finished\" with title \"Claude Code\"'"
}
]
}
}
The CLAUDE.md tells Claude Code what the conventions are (so it generates code that follows them), while the hooks enforce the conventions automatically (so even if Claude Code misses something, the tooling catches it).
Tips for Working with Hooks
Keep Hook Commands Fast
Hooks run synchronously, meaning Claude Code waits for them to complete. If your hook takes 30 seconds to run, there will be a 30-second pause after every matching tool invocation. Keep hooks fast by:
- Running only relevant checks (use file path filtering)
- Limiting output (use
headortail) - Using
--fixflags when available to auto-correct rather than just report
Use || true for Non-Critical Hooks
Append || true to hook commands that should not block Claude Code if they fail:
npx prettier --write "$CLAUDE_FILE_PATH" 2>/dev/null || true
Without || true, a failing hook could interrupt Claude Code's workflow.
Test Hooks Manually First
Before adding a hook, test the command manually to make sure it works:
# Test your hook command with a real file path
CLAUDE_FILE_PATH="src/index.ts" npx prettier --write "$CLAUDE_FILE_PATH"
Start Simple
Begin with one or two hooks and add more as needed. A common starting setup is:
- Prettier formatting on PostToolUse (Write|Edit)
- A notification on Stop
This covers the two most useful cases without overcomplicating your setup.
Hands-On Exercise
Set up hooks for your project:
- Create the settings file:
mkdir -p .claude
touch .claude/settings.json
- Add a basic hook configuration:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"command": "echo \"File modified: $CLAUDE_FILE_PATH\" >> .claude/changes.log"
}
]
}
}
- Start a Claude Code session and ask it to create or modify a file
- Check
.claude/changes.logto see the hook in action - Expand from there: add formatting, linting, or notification hooks
Summary
Hooks give you automated control over what happens during a Claude Code session. The four hook types (PreToolUse, PostToolUse, Notification, Stop) cover every stage of tool execution. By configuring hooks in .claude/settings.json, you can auto-format code, run linters, enforce team conventions, log activity, and send notifications -- all without manual intervention. Combined with CLAUDE.md for project context and custom slash commands for reusable workflows, hooks complete the picture of a fully customized Claude Code environment that works exactly the way you and your team need it to.
Cuestionario
Discussion
Sign in to join the discussion.

