Claude Code Hooks in Practice: AI Agent Automation Edition [2025 Latest]¶
Key Takeaways
- Claude Code Hooks has expanded from the initial 4 types to 11 event types, evolving into a system that covers the entire session lifecycle
- Hook execution types now include command (shell) / prompt (LLM evaluation) / agent (sub-agent), handling everything from simple script execution to context-aware decision making
- PreToolUse input modification, Plugin hooks, MCP integration, and hook definitions in Skills/Subagents frontmatter significantly strengthen ecosystem-wide integration
- CVE-2025-59536 reported in July 2025 has been patched, but configuration file handling still requires careful attention
Introduction¶
For those who have learned the basic concepts in the Claude Code Hooks Complete Guide, this article covers practical Hooks implementation patterns based on the latest specification and cutting-edge AI agent automation techniques.
This is a revised edition reflecting new features and specification changes added since the initial publication in January 2025. The Hooks specification has been significantly expanded with Claude Code version updates.
What Are Claude Code Hooks?¶
Claude Code Hooks are mechanisms that automatically execute actions at specific lifecycle points during AI agent execution. Unlike prompt-based "requests," they function as a deterministic control layer that executes reliably when conditions are met.
While instructions in CLAUDE.md or prompts are "suggestions the model is likely to follow," Hooks are "guardrails that always execute" — this is the fundamental difference.
11 Hook Event Types¶
Significantly expanded from the initial 4, the following 11 event types are now available.
| Event | Trigger Timing | Primary Use Cases |
|---|---|---|
| PreToolUse | Before tool execution | Command validation, dangerous operation blocking, input auto-correction |
| PermissionRequest | When permission dialog appears (v2.0.45+) | Automatic permission approval/denial |
| PostToolUse | After tool execution | Code formatting, quality checks, logging |
| Notification | When notification events occur | Desktop notifications, Slack integration |
| UserPromptSubmit | When user submits a prompt | Prompt validation, context injection |
| Stop | When main agent response completes | Task completion verification, auto-commit |
| SubagentStop | When sub-agent completes | Sub-agent completion verification |
| SubagentStart | When sub-agent launches | Sub-agent monitoring, context injection |
| PreCompact | Before compact operation | Transcript backup |
| SessionStart | At session start | Environment variable setup, dev context loading |
| SessionEnd | At session end | Cleanup, log aggregation |
→ See Command Reference for the full list of 15 event types
3 Hook Execution Types¶
In addition to traditional shell command execution, LLM-based evaluation and sub-agent verification have been added.
1. Command Hook (Shell Command)¶
Traditional shell command execution. Receives event JSON input via stdin and returns results through exit code and stdout.
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-style.sh"
}
2. Prompt Hook (LLM Evaluation)¶
Sends a prompt to a lightweight Claude model (Haiku) and returns approve/block decisions in JSON format. Particularly effective for "did the task really complete?" verification in Stop/SubagentStop.
{
"type": "prompt",
"prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete and no errors remain.",
"timeout": 30
}
3. Agent Hook (Sub-agent)¶
Launches a sub-agent that can use tools like Read, Grep, and Glob to verify conditions before returning a decision. Useful when more complex verification logic is needed.
Implementation Guide: Basic Configuration¶
Configuration Files¶
Claude Code Hooks are configured in the following settings files.
~/.claude/settings.json— User settings.claude/settings.json— Project settings (tracked by Git).claude/settings.local.json— Local project settings (not tracked by Git)- Enterprise managed policy settings — Administrator settings
Configuration Structure¶
{
"hooks": {
"EventName": [
{
"matcher": "ToolPattern",
"hooks": [
{
"type": "command",
"command": "your-command-here",
"timeout": 60
}
]
}
]
}
}
The matcher is a regex pattern for tool names (case-sensitive). You can specify multiple with pipes like Edit|Write, use wildcards like Notebook.*, or use * or empty string to match all tools.
The matcher is used in PreToolUse, PermissionRequest, and PostToolUse. For UserPromptSubmit, Stop, SubagentStop, etc., the matcher field is omitted.
/hooks Interactive Manager¶
Type /hooks within Claude Code to launch the interactive hook manager. You can view, add, and delete hooks without directly editing configuration files.
Temporarily Disabling Hooks¶
Add "disableAllHooks": true to your settings file, or use the toggle in the /hooks menu to temporarily disable all hooks. Note that hooks set by administrator policies cannot be disabled with this setting.
Practical Examples¶
Example 1: Automatic Code Quality Checks (PostToolUse)¶
Automatically run lint/format after file writes.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/lint-check.sh"
}
]
}
]
}
}
Example .claude/hooks/lint-check.sh implementation:
#!/bin/bash
input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path')
if [[ "$file_path" == *.py ]]; then
ruff check "$file_path" && ruff format "$file_path"
elif [[ "$file_path" == *.js ]] || [[ "$file_path" == *.ts ]]; then
npx eslint "$file_path" --fix && npx prettier --write "$file_path"
fi
Performance Note
Running formatters in PostToolUse consumes context window with every file change. For high-frequency edits, running formatting collectively in a Stop hook is more efficient.
Example 2: Blocking Dangerous Commands (PreToolUse)¶
PreToolUse hooks can block tool execution by returning exit code 2. Additionally, JSON output with permissionDecision enables precise control.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-dangerous.sh"
}
]
}
]
}
}
Example .claude/hooks/block-dangerous.sh implementation:
#!/bin/bash
input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command')
# Check for dangerous command patterns
if echo "$command" | grep -qE '(rm -rf /|sudo rm|chmod 777|DROP TABLE)'; then
# Precise control via JSON output
cat <<EOF
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Dangerous command pattern detected: $command"
}
}
EOF
exit 0
fi
exit 0
Example 3: Auto-Correcting Tool Input (PreToolUse updatedInput)¶
Since v2.0.10, PreToolUse hooks can modify input parameters before tool execution. This enables transparent sandboxing and automatic security enforcement for Claude.
#!/bin/bash
input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')
command=$(echo "$input" | jq -r '.tool_input.command // empty')
# Auto-convert git push to dry-run
if [[ "$tool_name" == "Bash" ]] && echo "$command" | grep -q "git push"; then
cat <<EOF
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "Auto-converted to dry-run",
"updatedInput": {
"command": "${command} --dry-run"
}
}
}
EOF
exit 0
fi
exit 0
Example 4: Context Injection at Session Start (SessionStart)¶
SessionStart hooks can automatically load development context and persist environment variables.
{
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/session-init.sh"
}
]
}
]
}
}
Example .claude/hooks/session-init.sh implementation:
#!/bin/bash
# Persist environment variables (CLAUDE_ENV_FILE is SessionStart-only)
if [ -n "$CLAUDE_ENV_FILE" ]; then
echo 'export NODE_ENV=development' >> "$CLAUDE_ENV_FILE"
fi
# Inject context information via JSON output
recent_changes=$(git log --oneline -5 2>/dev/null || echo "No git history")
open_issues=$(git log --oneline --all --grep="TODO" -3 2>/dev/null || echo "No issues found")
cat <<EOF
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "Recent changes:\n${recent_changes}\n\nOpen items:\n${open_issues}"
}
}
EOF
exit 0
Example 5: Intelligent Stop Hook (Prompt Type)¶
Use an LLM-based Prompt hook to evaluate task completion status with context awareness.
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "You are evaluating whether Claude should stop working. Context: $ARGUMENTS\n\nAnalyze and determine if:\n1. All user-requested tasks are complete\n2. Any errors need to be addressed\n3. Tests have been run and pass\n\nRespond with JSON: {\"decision\": \"approve\" or \"block\", \"reason\": \"your explanation\"}",
"timeout": 30
}
]
}
]
}
}
Advanced Usage Patterns¶
1. MCP Tools Integration¶
Hooks can be configured for tools provided via MCP (Model Context Protocol). MCP tools follow the mcp__<server>__<tool> naming pattern.
{
"hooks": {
"PreToolUse": [
{
"matcher": "mcp__memory__.*",
"hooks": [
{
"type": "command",
"command": "echo '[MCP] Memory operation' >> ~/mcp-audit.log"
}
]
},
{
"matcher": "mcp__.*__write.*",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/validate-mcp-write.py"
}
]
}
]
}
}
2. Plugin Hooks¶
Plugins can provide hooks that integrate seamlessly with user and project hooks. Plugin hooks are auto-merged when the plugin is enabled and execute in parallel with existing hooks.
{
"description": "Automatic code formatting plugin",
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",
"timeout": 30
}
]
}
]
}
}
In plugins, the ${CLAUDE_PLUGIN_ROOT} environment variable provides the absolute path to the plugin directory.
3. Hook Definitions in Skills/Subagents Frontmatter¶
In addition to configuration files, hooks can be defined directly in Skills and Subagents YAML frontmatter. These hooks are scoped to the component lifecycle and only execute while that component is active.
---
name: secure-operations
description: Perform operations with security checks
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/security-check.sh"
---
Sub-agent Stop hooks are automatically converted to SubagentStop.
4. Audit Logging and PermissionRequest Auto-Control¶
The PermissionRequest hook (v2.0.45+) automates responses to permission dialogs.
{
"hooks": {
"PermissionRequest": [
{
"matcher": "Read",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/auto-approve-reads.sh"
}
]
}
]
}
}
#!/bin/bash
# .claude/hooks/auto-approve-reads.sh
input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')
# Auto-approve reading document files
if [[ "$file_path" == *.md ]] || [[ "$file_path" == *.txt ]] || [[ "$file_path" == *.json ]]; then
cat <<EOF
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow"
}
}
}
EOF
exit 0
fi
# Otherwise, fall through to default permission flow
exit 0
→ For permission configuration details, see the Auto-Approval Settings Guide
5. Multi-Agent Orchestration¶
Combining SubagentStop hooks with Stop hooks enables automated task handoff between sub-agents.
{
"hooks": {
"SubagentStop": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/subagent-handoff.sh"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-pipeline.sh"
}
]
}
]
}
}
Checking the stop_hook_active flag to prevent infinite loops is important.
→ For Agent Teams details, see the Agent Teams Guide
Hook Input/Output Details¶
Input (stdin JSON)¶
All hooks receive JSON via stdin containing the following common fields.
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.jsonl",
"cwd": "/path/to/project",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
},
"tool_use_id": "toolu_01ABC123..."
}
Output Control¶
Two methods are available (mutually exclusive).
Method 1: Exit Code
exit 0— Success (stdout displayed in verbose mode. For UserPromptSubmit/SessionStart, added as context)exit 2— Block (stderr notified to Claude as error message. JSON output is ignored)- Other — Non-blocking error (stderr displayed in verbose mode)
Method 2: JSON Output (combined with exit 0)
{
"continue": true,
"stopReason": "Custom stop message",
"suppressOutput": false,
"systemMessage": "Warning shown to user",
"decision": "block",
"reason": "Explanation",
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "Auto-approved",
"updatedInput": {},
"additionalContext": "Extra context for Claude"
}
}
Security Considerations¶
CVE-2025-59536: RCE Vulnerability via Configuration Files¶
In July 2025, Check Point Research reported an arbitrary code execution vulnerability through .claude/settings.json. There was a risk of hooks auto-executing when cloning malicious repositories, and Anthropic released a fix in August 2025.
Security Best Practices¶
- Always validate and sanitize input in hook scripts
- Always quote shell variables (use
"$VAR", not$VAR) - Check for path traversal (
..) in file paths - Use absolute paths or
$CLAUDE_PROJECT_DIRfor script paths - Avoid accessing sensitive files such as
.env,.git/, and key files - Hook configuration changes are captured as snapshots at session startup and mid-session changes are not immediately applied (review via
/hooksmenu is required)
Enterprise Management¶
The allowManagedHooksOnly setting restricts hooks to only those approved by the organization. Hooks configured via administrator policies cannot be disabled by the user-side disableAllHooks setting.
Troubleshooting¶
Debugging Steps¶
- Check registration status with
/hooks - View execution details with
claude --debug - Manually execute hook commands to test
- Verify script execution permissions (
chmod +x) - Validate JSON configuration syntax
Common Issues¶
Hook not firing
- Verify the matcher tool name is exact (case-sensitive:
Bash, notbash) - Verify event names are PascalCase (
PreToolUse, notpreToolUse)
JSON output not being parsed
- Check that shell profile (
.bashrc, etc.) output is not contaminating stdout - JSON output is only processed with exit code 0 (exit 2 uses only stderr)
Timeout issues
- Default is 60 seconds. Each hook can set a custom
timeoutfield
{
"type": "command",
"command": "your-slow-command",
"timeout": 120
}
Environment Variable Reference¶
| Variable | Description | Available In |
|---|---|---|
$CLAUDE_PROJECT_DIR | Absolute path to project root | All hooks |
$CLAUDE_ENV_FILE | File path for persisting environment variables | SessionStart only |
$CLAUDE_CODE_REMOTE | "true" in remote (web) environments, unset in local CLI | All hooks |
${CLAUDE_PLUGIN_ROOT} | Absolute path to plugin directory | Plugin hooks |
Summary¶
Claude Code Hooks has evolved from an initial system of 4 events with command-only execution to an ecosystem-integrated automation platform with 11 events and 3 execution types. With Plugin hooks, MCP integration, and definitions in Skills/Subagents frontmatter, integration with Claude Code's features has deepened, enabling deterministic control across the entire development workflow.
When properly implemented, it enables the transition from "expectations of the model" to "guaranteed execution." However, since hooks execute arbitrary commands with user permissions, security considerations are essential.
Related Articles¶
Fundamentals of 14 event types and 3 handler types
Hooks Advanced - Production Implementation
Error handling and team development applications
All commands, settings, and shortcuts organized by usage level
Safe auto-approval mode operations
Related Resources¶
- Claude Code Hooks Official Reference
- Claude Code Hooks Guide (Introduction)
- Claude Code Plugins Official Documentation
- awesome-claude-code (Community Hooks Collection)
- claude-code-hooks-mastery (Implementation Samples)
This article was originally published on January 18, 2025, and fully revised on February 27, 2026 based on the latest specification. Please refer to the official documentation for the most current information.