13.2 Hooks: Automated Guardrails and Checks
Course: Claude Code - Enterprise Development Section: Extending Claude Code Video Length: 3-4 minutes Presenter: Daniel Treasure
Opening Hook
"You've set up specialized agents with the right tools. Now, how do you make sure they actually follow your standards? Hooks are the automation layer. They intercept tool use, run validation, block risky operations, and trigger enforcement—all without the model reasoning about it. Let me show you how."
Key Talking Points
1. Five Hook Events
What to say: "Hooks fire at five key moments in a workflow. PreToolUse—right before a tool runs, you can inspect it, modify it, or block it. PostToolUse—after it succeeds, validate the result or trigger downstream work. PostToolUseFailure—when a tool fails, decide if the agent should retry or stop. Notification—broadcast events to logging or Slack. UserPromptSubmit—inspect user input before Claude sees it."
What to show on screen: - Show timeline diagram: User Request → PreToolUse hook → Tool Execution → PostToolUse/PostToolUseFailure hook → Continue → Notification hook - Highlight each event with a color: PreToolUse (red/block), PostToolUse (green/validate), PostToolUseFailure (yellow/retry), Notification (blue/broadcast), UserPromptSubmit (orange/filter)
2. Hooks Run Without Model Reasoning
What to say: "This is critical. Hooks don't ask Claude 'is this safe?' Hooks are shell commands. Fast. Deterministic. You define the rule—if this command returns zero, allow it; if non-zero, block it. The model never reasons about the hook. That's how you enforce policy at scale without slowing everything down."
What to show on screen:
- Open settings.json hooks section
- Show a PreToolUse hook: matcher: "Bash", command: "./validate.sh"
- Run the validation script manually to show what happens: script checks for dangerous patterns, returns 0 (pass) or non-zero (fail)
3. Matchers: Tool Name Regex
What to say: "You don't hook everything. You use a matcher—a regex that targets specific tools. Hook only Bash, not Read. Hook only Edit and Write, not Bash. This keeps your hooks focused and fast. The matcher can be a single tool name or a regex like 'Edit|Write' to match multiple."
What to show on screen:
- Show settings.json with three different hooks:
- PreToolUse with matcher: "Bash" (single tool)
- PostToolUse with matcher: "Edit|Write" (regex, multiple tools)
- PostToolUse with matcher: ".*" (all tools—rare, but possible)
4. Three Common Patterns
What to say: "First pattern: linting. After Edit or Write, run your linter. Catch style issues before they hit the codebase. Second pattern: security scanning. PreToolUse on Bash—check for dangerous commands before they run. Third pattern: approval gates. PostToolUseFailure—if a critical operation fails, pause and notify a human."
What to show on screen:
- Demo 1: PostToolUse hook on Write → runs black --check → catches style violations
- Demo 2: PreToolUse hook on Bash → checks against blocklist (rm, kill, etc.) → rejects or modifies command
- Demo 3: PostToolUseFailure hook → logs to Slack webhook → human reviews before retry
5. Hooks in Subagent Frontmatter
What to say: "You can also attach hooks directly to a subagent. Same syntax, same matchers. A CodeFormatter agent might have a PostToolUse lint hook built into its AGENT.md. That way, the hook runs only when that agent edits files—not when you do. Separation of concerns at its finest."
What to show on screen:
- Open an AGENT.md file with hooks in frontmatter
- Show the hooks section under the --- divider
- Explain: these hooks apply only to this agent's tool use, not globally
Demo Plan
- (0:30) Open settings.json, show hooks section structure (PreToolUse, PostToolUse arrays)
- (1:00) Show a PreToolUse example: Bash tool, dangerous-commands.sh validator, returns 1 if blocked
- (1:30) Demo: ask Claude to run
rm -rf /, hook intercepts, shows error message - (2:00) Show PostToolUse example: Edit tool, runs black formatter, logs result
- (2:30) Open AGENT.md with embedded hooks; explain subagent-scoped enforcement
- (3:00) Discuss: when to use global hooks (policy enforcement) vs. subagent hooks (role-specific checks)
- (3:30) Show notification hook example: logs to JSON file or webhook
Code Examples & Commands
settings.json with multiple hooks
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "./scripts/validate-bash-command.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "python -m black --check {filepath}"
}
]
},
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "echo \"Tool {toolName} executed on {filepath}\" >> .claude/audit.log"
}
]
}
],
"PostToolUseFailure": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "./scripts/notify-on-bash-failure.sh"
}
]
}
]
}
}
Bash validation script example
#!/bin/bash
# validate-bash-command.sh
# Blocks dangerous commands. Returns 1 (fail) if blocked, 0 (pass) if allowed.
COMMAND="$1"
BLOCKED_PATTERNS=(
"rm -rf /"
"kill -9"
"dd if=/dev/zero"
"> /etc/"
)
for pattern in "${BLOCKED_PATTERNS[@]}"; do
if [[ "$COMMAND" == *"$pattern"* ]]; then
echo "ERROR: Blocked command pattern: $pattern"
exit 1
fi
done
exit 0
AGENT.md with embedded hooks
---
name: CodeFormatter
description: "Formats code and validates style after editing."
tools: [Write, Edit, Bash]
model: haiku
hooks:
- event: PostToolUse
matcher: "Write|Edit"
hooks:
- type: command
command: "python -m flake8 {filepath} && python -m black {filepath}"
---
You are a Python code formatter. After making edits, ensure code is properly formatted.
Notification hook (log to file)
{
"hooks": {
"Notification": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "echo \"$(date) - {eventType} on {toolName}\" >> .claude/events.log"
}
]
}
]
}
}
Gotchas & Tips
Hook commands must be fast. If your validation script takes 30 seconds, you've created a bottleneck. Prefer regex patterns, fast linters, or quick static checks. Save deep analysis for background jobs.
Exit codes matter. Return 0 (success) to allow the tool use. Return non-zero to block. The hook output goes to the user and Claude as an error message. Make it informative.
The matcher is a regex, not a string. "Bash" matches only Bash. "Edit|Write" matches both. ".*" matches everything (avoid unless necessary). Test your regex.
Hooks can modify tool input, but it's rare. Most hooks validate or block. Modifying a command before it runs is powerful but easy to get wrong. Document heavily if you do it.
SubagentStart and SubagentStop hooks exist too. You can run setup/cleanup when an agent starts or stops. Rarely needed, but useful for resource management or logging.
Notification hooks can't block. They're fire-and-forget. Use them to log, alert, or trigger external systems, but Claude won't wait for the result.
Lead-out
"Hooks are how you scale trust. Instead of reviewing every Edit and Write, you define the rule once and it applies everywhere. Next video, we'll look at Agent Skills—predefined capabilities that agents can invoke without expanding their prompt. They're like hooks' opposite: instead of blocking bad things, they enable good things in a structured way."
Reference URLs
- Claude Code hooks documentation
- Hook events reference (PreToolUse, PostToolUse, etc.)
- Shell command scripting best practices
- settings.json schema
Prep Reading
- Security patterns with hooks
- Performance considerations for hook execution
- Logging and audit trails with hooks
- Integration with external systems (Slack, webhooks)
Notes for Daniel: Have a working validation script ready (validate-bash-command.sh) to demo live. The Bash interception is the most visually compelling part of hooks. If you have Slack set up, use the Notification hook to send a real message—that makes it concrete. Pre-populate settings.json with 2-3 examples so you don't spend time typing. Emphasize the speed/determinism angle—hooks don't think, they just enforce.