Claude Code /hooks: Make AI Follow Your Rules
What Is /hooks
When using Claude Code, have you ever wished for:
- Automatically running lint before Claude Code executes
git push? - Sending a summary to Slack when a session ends?
- Having another AI review the code before Claude Code deletes a file?
That’s exactly what /hooks does. It’s Claude Code’s event hook system — automatically executing your predefined logic when specific events occur.
If /permissions controls “whether something can be done,” then /hooks controls “what else happens before and after it’s done.”
Core Concepts
Events
The hook system revolves around “events.” Claude Code triggers various events during operation, and you can attach hooks to any of them.
Overview of common events:
| Event | When It Fires | Match Dimension |
|---|---|---|
| PreToolUse | Before tool execution | Tool name (e.g., Bash, Edit) |
| PostToolUse | After tool execution | Tool name |
| PostToolUseFailure | After tool execution fails | Tool name |
| UserPromptSubmit | When user submits a message | — |
| Notification | Notification events | Notification type |
| SessionStart | Session begins | Source (startup/resume/clear/compact) |
| SessionEnd | Session ends | End reason |
| Stop | Claude stops responding | — |
| SubagentStart | Sub-agent launches | Agent type |
| SubagentStop | Sub-agent stops | Agent type |
| PreCompact | Before context compaction | — |
| PostCompact | After context compaction | — |
| PermissionRequest | Permission prompt appears | Tool name |
| PermissionDenied | Permission denied | Tool name |
| ConfigChange | Configuration changes | Config source |
| FileChanged | File changes | Filename |
| CwdChanged | Working directory changes | — |
| InstructionsLoaded | Instruction files loaded | Load reason |
| Elicitation | MCP server asks a question | Server name |
| ElicitationResult | MCP question result | Server name |
| Setup | Initialization | Trigger type (init/maintenance) |
| WorktreeCreate | Worktree created | — |
| WorktreeRemove | Worktree removed | — |
| TaskCreated | Task created | — |
| TaskCompleted | Task completed | — |
| TeammateIdle | Teammate idle | — |
27 events in total, covering virtually every key moment in Claude Code’s operation.
Matchers
Each event can have multiple hooks attached, using a matcher to determine when they fire.
For example, with the PreToolUse event, you can configure different hooks for different tools:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "echo 'about to run bash'" }]
},
{
"matcher": "Edit|Write",
"hooks": [{ "type": "command", "command": "echo 'about to modify file'" }]
}
]
}
}
Three matching patterns are supported:
| Pattern | Example | Meaning |
|---|---|---|
| Exact match | "Bash" | Matches only the Bash tool |
| Pipe-separated | "Bash|Write|Edit" | Matches any of these |
| Regular expression | "Bash|mcp__.*" | Matches Bash or any MCP tool |
Leave empty or use "*" to match all cases for that event.
Four Hook Types
1. command — Shell Command
The most basic and commonly used type — directly executes a shell command:
{
"type": "command",
"command": "npm run lint",
"timeout": 30000
}
Hooks receive JSON context via stdin (tool name, tool input, session ID, etc.), return results via stdout, and control behavior via exit codes:
| Exit Code | Meaning |
|---|---|
| 0 | Success — stdout content is added to conversation context |
| 2 | Blocking — stderr content informs Claude, operation is blocked |
| Other | Non-blocking error — stderr shown as a warning |
2. prompt — Single-Turn AI Judgment
Uses a lightweight AI model for single-turn evaluation — ideal for “smart review” scenarios:
{
"type": "prompt",
"prompt": "Check if this command might delete important files. If risky, return {\"decision\": \"block\", \"reason\": \"may delete important files\"}, otherwise return {\"decision\": \"approve\"}",
"timeout": 30000
}
Uses a small, fast model by default (typically Haiku). You can specify a different model via the model field.
3. agent — Multi-Turn AI Agent
When single-turn evaluation isn’t enough, use the Agent type — it launches a sub-agent capable of multi-turn conversation and tool use:
{
"type": "agent",
"prompt": "Review this code for security issues. If problems are found, explain them in detail",
"timeout": 60000
}
Agents can run up to 50 conversation turns with a default timeout of 60 seconds.
4. http — HTTP Request
Sends a POST request to an external service — ideal for CI/CD integration, notification systems, audit logs, etc.:
{
"type": "http",
"url": "https://your-webhook.example.com/hook",
"headers": {
"Authorization": "Bearer $API_TOKEN"
},
"allowedEnvVars": ["API_TOKEN"],
"timeout": 600000
}
HTTP hooks include SSRF protection. The request body is JSON-formatted hook input. Headers support environment variable interpolation, but variables must be explicitly declared in allowedEnvVars.
How to Use /hooks
In Claude Code interactive mode, type:
/hooks
This opens a read-only browsing panel that displays all active hooks organized by event. You can:
- Browse hierarchically: Events → Matchers → Individual hooks
- View detailed configuration for each hook (type, command, timeout, etc.)
- See which configuration source each hook comes from (User / Project / Local / Plugin / Session)
Note: The /hooks command itself is read-only. To add or modify hooks, edit settings.json.
Configuring Hooks in settings.json
Basic Structure
{
"hooks": {
"EventName": [
{
"matcher": "match pattern (optional)",
"hooks": [
{
"type": "command | prompt | agent | http",
"command": "...",
"timeout": 30000
}
]
}
]
}
}
Configuration Sources and Priority
Hooks can be configured at different levels, and hooks from all levels are all executed (merged, not overridden):
| Source | File Path |
|---|---|
| User | ~/.claude/settings.json |
| Project | <project>/.claude/settings.json |
| Local | <project>/.claude/settings.local.json |
| Plugin | ~/.claude/plugins/*/hooks/hooks.json |
| Managed | Enterprise managed policy |
This differs from permission rules — permissions use “later overrides earlier,” but hooks merge and all execute.
The if Condition Filter
All hook types support an if field for more precise filtering using permission rule syntax:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo 'dangerous command detected'",
"if": "Bash(rm -rf:*)"
}
]
}
]
}
}
This hook only fires when the Bash tool is about to execute a command starting with rm -rf. The if syntax is the same as the rule format in /permissions.
Practical Examples
Example 1: Auto-Approve with Logging
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo '{\"decision\": \"approve\"}'"
}
]
}
]
}
}
Example 2: Alert on Sensitive File Changes
{
"hooks": {
"FileChanged": [
{
"matcher": ".env|.envrc",
"hooks": [
{
"type": "command",
"command": "echo 'Warning: sensitive config file modified' | tee /dev/stderr && exit 2"
}
]
}
]
}
}
When .env or .envrc files are modified, an automatic warning is triggered. Exit code 2 blocks further operations.
Example 3: AI Review for Dangerous Commands
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "prompt",
"prompt": "Analyze the command from stdin. If it could cause irreversible data loss (e.g., rm -rf, DROP TABLE), return {\"decision\": \"block\", \"reason\": \"...\"}, otherwise return {\"decision\": \"approve\"}"
}
]
}
]
}
}
Example 4: Send Summary on Session End
{
"hooks": {
"SessionEnd": [
{
"hooks": [
{
"type": "http",
"url": "https://hooks.slack.com/services/xxx/yyy/zzz",
"timeout": 10000
}
]
}
]
}
}
Example 5: Run-Once Initialization Hook
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "echo 'Session initialized at $(date)'",
"once": true
}
]
}
]
}
}
once: true means this hook runs only once per session.
Hook Input and Output
Input (stdin)
Each hook receives a JSON object via stdin containing common fields and event-specific fields:
Common fields:
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.jsonl",
"cwd": "/your/project",
"permission_mode": "default"
}
Event-specific fields:
| Event | Additional Fields |
|---|---|
| PreToolUse | tool_name, tool_input |
| PostToolUse | tool_name, tool_input, tool_output |
| UserPromptSubmit | user_prompt |
| Notification | notification_type, message |
| SessionStart | source |
| SessionEnd | reason |
| FileChanged | file_path, event |
Output (stdout JSON)
Hooks can output structured JSON to control Claude Code’s behavior:
{
"continue": true,
"decision": "approve",
"reason": "The command appears safe",
"suppressOutput": false,
"systemMessage": "Note: this command has been automatically reviewed"
}
Core fields:
| Field | Type | Description |
|---|---|---|
continue | boolean | Whether to continue execution |
decision | string | "approve" or "block" |
reason | string | Human-readable explanation |
suppressOutput | boolean | Whether to hide hook output |
systemMessage | string | System message injected into the conversation |
PreToolUse-specific output:
{
"hookSpecificOutput": {
"permissionDecision": "allow",
"updatedInput": { "command": "npm test -- --coverage" },
"additionalContext": "Automatically added coverage parameter"
}
}
With updatedInput, you can modify the tool’s input before execution — one of the most powerful capabilities of the hook system.
Async Hooks
Some hooks don’t need to block the main flow. For notifications or logging, you don’t want Claude Code waiting for completion.
Two ways to enable async:
Method 1: Configuration Declaration
{
"type": "command",
"command": "curl -X POST https://example.com/log",
"async": true,
"asyncRewake": true
}
async: true runs the hook in the background. asyncRewake: true wakes the agent when the async hook completes to process the result.
Method 2: Runtime Declaration
The hook outputs {"async": true} as the first line of stdout to switch to async mode. This is useful when you need to decide at runtime whether to go async.
Permission Decision Priority
When multiple PreToolUse hooks make decisions about the same operation, the priority is:
deny > ask > allow
If any single hook says deny, the operation is rejected.
Important: A hook’s allow decision does NOT bypass deny/ask permission rules in settings.json. In other words, you cannot use hooks to “unlock” operations forbidden by the permission system.
Enterprise Controls
Enterprise administrators can control the hook system via managed policies:
| Policy | Effect |
|---|---|
allowManagedHooksOnly: true | Only managed hooks execute; user and project hooks are ignored |
disableAllHooks: true | All hooks disabled (including managed ones) |
When non-managed settings set disableAllHooks, only non-managed hooks are disabled — admin hooks can never be disabled by regular users.
Practical Tips
Tip 1: Use Command Hooks for Lightweight Checks
Shell command hooks start fast with minimal overhead — ideal for format checks, file existence validation, and other lightweight operations. Exit code 2 is your “emergency brake” — use it anytime to block an operation.
Tip 2: Use if to Narrow the Trigger Scope
Don’t trigger hooks for every Bash command. Use the if field for precise filtering:
{
"type": "command",
"command": "run-safety-check.sh",
"if": "Bash(docker:*)"
}
This triggers the safety check only for Docker commands, avoiding unnecessary overhead.
Tip 3: Use updatedInput for Command Enhancement
PreToolUse hooks can modify tool input. For example, automatically adding --save-exact to all npm install commands:
{
"type": "command",
"command": "read input && echo '{\"hookSpecificOutput\": {\"updatedInput\": {\"command\": \"'$(echo $input | jq -r .tool_input.command)' --save-exact\"}}}'"
}
Tip 4: Use once to Avoid Repetition
For initialization logic that only needs to run once (environment checks, dependency installation), add once: true to prevent repeated execution on every trigger.
Tip 5: Don’t Set Timeouts Too Long
Default timeouts: command has no limit, prompt 30 seconds, agent 60 seconds, http 10 minutes. Set a reasonable timeout based on actual needs to prevent hooks from stalling and slowing down the entire session.
Final Thoughts
The hook system is the most flexible extension mechanism in Claude Code.
/permissions answers “can it be done,” while /hooks answers “what else should happen when it’s done.” Master hooks, and you can turn Claude Code into a highly automated development workflow that follows your team’s standards.
But don’t over-engineer — start with one or two simple command hooks, like “run lint before git push” or “send notification on session end.” Once you’re comfortable with the hook input/output mechanism, gradually add more complex prompt and agent hooks.
Start simple, grow as needed.
More Articles
- Why AI-First Startups Only Need One Programming Language
- cc-ping: Ping All Your Claude Code Configs in One Command
- Shocking! This Tool Lets Programmers Finish 95 Minutes of Work in 4 Minutes! 24x Efficiency Boost
- CCBot - 24x Development Efficiency Boost
- Claude Code /add-dir: The Monorepo Command You Miss
- Claude Code Token-Saving Tip: The Power of the Exclamation Mark
- I Built a Bot That Runs Claude Code From Chat
- Claude Code /btw Command Explained: Quick Side Questions Without Breaking Flow
- Claude Code /compact: Free Up Context, Keep Progress
- Claude Code /config: Every Setting Explained
- Claude Code /context: What's Eating Your Context Window?
- Claude Code /diff: See Exactly What Changed, Turn by Turn
- Claude Code /fast: Same Opus, 2x Speed — Worth It?
- Best Practice for External Knowledge in Claude Code: GitHub MCP + Context7
- Claude Code /init: Generate CLAUDE.md in 10 Seconds
- Claude Code MCP: Give Your AI Access to Any Tool
- Claude Code /memory Explained: Make AI Truly Remember Your Project
- Claude Code /model: Opus vs Sonnet vs Haiku Guide
- Claude Code /permissions: Fine-Grained Control Over What AI Can Do
- Claude Code /plan Explained: Think Before You Code
- Claude Code + Playwright MCP: AI Can Finally "See" the Page
- Claude Code /resume Command Explained: Don't Let Your Conversations Go to Waste
- Claude Code /review: Let AI Do Your Code Review
- Claude Code Skills Explained: Build Your Custom Command Library
- Claude Code /stats: See How Much AI Does For You
- Claude Code /status Command Explained: Your Session Dashboard
- Claude Code /tasks Command Explained: Master Your Background Tasks
- Claude Code /usage Command Explained: Know Your Remaining Quota
- Claude Code /vim: Vim Keybindings in Your AI Coding Assistant
- Claude Code in 2026: The Only Setup Guide You Need
- The Complete Guide to Claude: From Chat to Code to Automation