Claude Code Hooks: Complete Guide to Automated Policies
Master Claude Code's Hooks system. Learn how to automatically enforce policies, validate actions, and create guardrails with PreToolUse, PostToolUse, and Stop hooks.
Hooks are Claude Code’s automation layer—they let you intercept, validate, and modify Claude’s actions automatically. This guide covers every hook type, configuration patterns, and practical use cases.
What are Hooks?
According to the official documentation, Hooks are:
- Event interceptors that run before/after Claude’s actions
- Policy enforcers that automatically validate behavior
- Guardrails that prevent dangerous operations
- Automations that trigger custom logic
Think of Hooks as middleware for Claude Code—they sit between Claude’s intentions and actual execution.
Hook Architecture
┌────────────────────────────────────────────────────────────┐
│ Claude Code CLI │
│ │
│ User Prompt ──────────────────────────────────────► │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ UserPromptSubmit│ Hook: Validate/modify prompt │
│ │ Hook │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ PreToolUse │ Hook: Approve/deny/modify action │
│ │ Hook │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Tool Execution │ Actual action (Read, Write, Bash) │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ PostToolUse │ Hook: React to results │
│ │ Hook │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Stop Hook │ Hook: Verify completion │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ Response ◄──────────────────────────────────────── │
└────────────────────────────────────────────────────────────┘
Hook Events
| Event | When Triggered | Common Use Cases |
|---|---|---|
SessionStart | Session begins | Load context, verify setup |
UserPromptSubmit | User sends prompt | Add context, validate input |
PreToolUse | Before tool runs | Approve/deny actions |
PostToolUse | After tool runs | Audit, react to results |
Stop | Claude stops | Verify completion |
SubagentStop | Subagent completes | Ensure task completion |
Hook Configuration
Hooks are configured in .claude/settings.json:
{
"hooks": {
"PreToolUse": [...],
"PostToolUse": [...],
"Stop": [...],
"SessionStart": [...],
"UserPromptSubmit": [...],
"SubagentStop": [...]
}
}
Hook Structure
{
"hooks": {
"EventName": [
{
"matcher": "ToolName or *",
"hooks": [
{
"type": "prompt | command",
"prompt": "Instructions for Claude",
"command": "shell command to run",
"onFailure": "block | warn | ignore"
}
]
}
]
}
}
PreToolUse Hooks
Purpose: Intercept actions before they execute. Can approve, deny, or modify.
Block Dangerous Commands
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "prompt",
"prompt": "Check if command contains destructive operations (rm -rf, DROP TABLE, git push --force). If yes, return 'ask' for confirmation. Otherwise return 'approve'."
}]
}]
}
}
Validate File Access
{
"hooks": {
"PreToolUse": [{
"matcher": "Read",
"hooks": [{
"type": "prompt",
"prompt": "If the file path contains .env, secrets, or credentials, return 'block' with explanation. Otherwise return 'approve'."
}]
}]
}
}
Security Scan Before Write
{
"hooks": {
"PreToolUse": [{
"matcher": "Write",
"hooks": [{
"type": "prompt",
"prompt": "Scan the content for potential secrets (API keys, passwords, tokens). If found, return 'block' with the line numbers. Otherwise return 'approve'."
}]
}]
}
}
SQL Injection Prevention
{
"hooks": {
"PreToolUse": [{
"matcher": "Write",
"hooks": [{
"type": "prompt",
"prompt": "If the code contains string concatenation with SQL keywords (SELECT, INSERT, UPDATE, DELETE), return 'block' with SQL injection warning. Otherwise return 'approve'."
}]
}]
}
}
PostToolUse Hooks
Purpose: React after an action completes. Good for logging, auditing, and triggering follow-up.
Audit File Changes
{
"hooks": {
"PostToolUse": [{
"matcher": "Write",
"hooks": [{
"type": "prompt",
"prompt": "Log the file path and a summary of changes. If the file is in auth/, payment/, or admin/, note that security review is recommended."
}]
}]
}
}
Auto-Run Tests After Edit
{
"hooks": {
"PostToolUse": [{
"matcher": "Edit",
"hooks": [{
"type": "command",
"command": "npm test --passWithNoTests",
"onFailure": "warn"
}]
}]
}
}
Trigger Security Scan
{
"hooks": {
"PostToolUse": [{
"matcher": "Write",
"hooks": [{
"type": "prompt",
"prompt": "If the written file is in auth/, payment/, or admin/ directories, trigger security-auditor agent for review."
}]
}]
}
}
Stop Hooks
Purpose: Verify completion criteria before Claude stops. Essential for quality gates.
Verify Tests Run
{
"hooks": {
"Stop": [{
"matcher": "*",
"hooks": [{
"type": "prompt",
"prompt": "Check if code was modified (Write or Edit used). If yes, verify tests were run. If no tests ran, block and explain that tests are required before completion."
}]
}]
}
}
Security Review Required
{
"hooks": {
"Stop": [{
"matcher": "*",
"hooks": [{
"type": "prompt",
"prompt": "If code in auth/, payment/, or admin/ was modified, verify security-auditor was run. If not, block and explain security review requirement."
}]
}]
}
}
Documentation Check
{
"hooks": {
"Stop": [{
"matcher": "*",
"hooks": [{
"type": "prompt",
"prompt": "If public API was modified, verify documentation was updated. If not, remind about documentation update before approving."
}]
}]
}
}
Coverage Gate
{
"hooks": {
"Stop": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "npm run test:coverage -- --coverageThreshold='{\"global\":{\"lines\":80}}'",
"onFailure": "block"
}]
}]
}
}
SessionStart Hooks
Purpose: Initialize context when a session begins.
Load Project Context
{
"hooks": {
"SessionStart": [{
"matcher": "*",
"hooks": [{
"type": "prompt",
"prompt": "Read CLAUDE.md and summarize key project policies. Load any saved memory entities related to recent work."
}]
}]
}
}
Environment Check
{
"hooks": {
"SessionStart": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "node --version && npm --version",
"onFailure": "warn"
}]
}]
}
}
UserPromptSubmit Hooks
Purpose: Process user input before Claude acts.
Add Context
{
"hooks": {
"UserPromptSubmit": [{
"matcher": "*",
"hooks": [{
"type": "prompt",
"prompt": "If the prompt mentions 'deploy' or 'release', add reminder about running full test suite first."
}]
}]
}
}
Keyword Detection
{
"hooks": {
"UserPromptSubmit": [{
"matcher": "*",
"hooks": [{
"type": "prompt",
"prompt": "If prompt contains 'urgent' or 'quickly', add reminder to maintain code quality despite time pressure."
}]
}]
}
}
SubagentStop Hooks
Purpose: Verify subagent task completion.
Verify Subagent Results
{
"hooks": {
"SubagentStop": [{
"matcher": "*",
"hooks": [{
"type": "prompt",
"prompt": "Review subagent results. If the task was security-related and no security-auditor was used, flag for review."
}]
}]
}
}
Hook Types
Prompt Hooks
Use Claude to evaluate conditions:
{
"type": "prompt",
"prompt": "Your evaluation instructions..."
}
Prompt Hook Responses:
approve- Allow the actiondenyorblock- Block the actionask- Ask user for confirmation- Any other text - Treated as modification/context
Command Hooks
Run shell commands:
{
"type": "command",
"command": "npm test",
"onFailure": "block | warn | ignore"
}
onFailure Options:
block- Stop if command failswarn- Show warning but continueignore- Silently ignore failure
Matchers
Specific Tool Matcher
{
"matcher": "Bash"
}
Wildcard Matcher
{
"matcher": "*"
}
Multiple Tools
Create separate entries for each tool:
{
"hooks": {
"PreToolUse": [
{ "matcher": "Write", "hooks": [...] },
{ "matcher": "Edit", "hooks": [...] },
{ "matcher": "Bash", "hooks": [...] }
]
}
}
Practical Examples
Example 1: Security-First Configuration
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{
"type": "prompt",
"prompt": "Block if command contains: rm -rf, DROP, TRUNCATE, --force, sudo rm. Return 'block' with explanation, or 'approve'."
}]
},
{
"matcher": "Read",
"hooks": [{
"type": "prompt",
"prompt": "Block if path contains: .env, secret, credential, private, key. Return 'block' or 'approve'."
}]
},
{
"matcher": "Write",
"hooks": [{
"type": "prompt",
"prompt": "Scan for secrets (API keys starting with sk_, tokens, passwords). Block if found."
}]
}
],
"PostToolUse": [
{
"matcher": "Write",
"hooks": [{
"type": "prompt",
"prompt": "If file in auth/payment/admin, recommend security-auditor review."
}]
}
],
"Stop": [
{
"matcher": "*",
"hooks": [{
"type": "prompt",
"prompt": "If auth/payment code modified, verify security-auditor ran. Block if not."
}]
}
]
}
}
Example 2: Quality Gate Configuration
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [{
"type": "command",
"command": "npm run lint -- --quiet",
"onFailure": "warn"
}]
},
{
"matcher": "Edit",
"hooks": [{
"type": "command",
"command": "npm run typecheck",
"onFailure": "warn"
}]
}
],
"Stop": [
{
"matcher": "*",
"hooks": [{
"type": "prompt",
"prompt": "Verify: 1) Tests ran and passed, 2) Lint passed, 3) Types check. Block if any failed."
}]
},
{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "npm test -- --passWithNoTests",
"onFailure": "block"
}]
}
]
}
}
Example 3: Audit Trail Configuration
{
"hooks": {
"SessionStart": [
{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "echo \"Session started: $(date)\" >> .claude/audit.log",
"onFailure": "ignore"
}]
}
],
"PostToolUse": [
{
"matcher": "Write",
"hooks": [{
"type": "prompt",
"prompt": "Log: file path, action type, timestamp. Append to .claude/audit.log"
}]
},
{
"matcher": "Bash",
"hooks": [{
"type": "prompt",
"prompt": "Log: command executed, exit status. Append to .claude/audit.log"
}]
}
]
}
}
Example 4: Team Workflow Configuration
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{
"type": "prompt",
"prompt": "If command is 'git push', verify: 1) Tests pass, 2) Lint passes, 3) PR exists or being created. Block push without PR."
}]
}
],
"Stop": [
{
"matcher": "*",
"hooks": [{
"type": "prompt",
"prompt": "If feature work completed, remind to: 1) Update documentation, 2) Create/update PR, 3) Request review."
}]
}
]
}
}
Hooks vs Other Tools
| Feature | Hooks | Permissions | CLAUDE.md |
|---|---|---|---|
| Automatic | ✅ Yes | ✅ Yes | ❌ Manual |
| Conditional | ✅ Yes | ❌ No | ❌ No |
| Custom logic | ✅ Yes | ❌ No | ❌ No |
| Shell commands | ✅ Yes | ❌ No | ❌ No |
| Easy config | Medium | Easy | Easy |
When to Use Hooks
Use Hooks when:
- You need conditional logic
- You want automatic enforcement
- You need to run external commands
- You want audit trails
Use Permissions when:
- Simple allow/deny rules
- No conditional logic needed
- Static file access control
Use CLAUDE.md when:
- Guidelines and preferences
- Context and documentation
- Non-enforced recommendations
Debugging Hooks
Hook Not Triggering
- Check matcher matches the tool name exactly
- Verify JSON syntax is correct
- Check hooks array structure
Command Hook Failing
- Test command manually in terminal
- Check command path and permissions
- Review onFailure setting
Prompt Hook Not Working
- Make prompt instructions clear
- Ensure response options are explicit
- Test with simpler prompt first
Best Practices
1. Start Simple
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "prompt",
"prompt": "Block rm -rf commands. Return 'block' or 'approve'."
}]
}]
}
}
2. Be Explicit About Responses
{
"type": "prompt",
"prompt": "Evaluate X. Return exactly one of: 'approve', 'block', or 'ask'."
}
3. Use onFailure Appropriately
// Critical: block on failure
{ "command": "npm test", "onFailure": "block" }
// Important: warn on failure
{ "command": "npm run lint", "onFailure": "warn" }
// Optional: ignore on failure
{ "command": "npm run optional-check", "onFailure": "ignore" }
4. Layer Your Hooks
PreToolUse → Prevent bad actions
PostToolUse → Audit and react
Stop → Verify completion
Getting Started
Today:
- Add one PreToolUse hook for dangerous commands
- Add one Stop hook for test verification
- Test with simple scenarios
This week:
- Build out security hooks
- Add quality gate hooks
- Test with real workflows
This month:
- Develop comprehensive hook configuration
- Add team-specific policies
- Create audit trail system
Hooks transform Claude Code from a reactive assistant to a policy-enforcing partner. Set them once, trust them always.
Sources: Claude Code Documentation, Claude Code GitHub