Claude Code hooks configuration patterns, event types, validation scripts, and decision control for automating workflows and integrating external tools...
This skill provides assistance for working with Claude Code hooks to enable automation of workflows and integration of external tools through an event-driven system.
Hooks are event-driven automation scripts that execute automatically when specific events occur during Claude Code's operation. Think of them as "middleware" for Claude's actions—they intercept events to validate inputs, enrich context, automate workflows, or integrate external tools, transforming Claude from a general assistant into a policy-aware, context-enriched agent.
Every hook consists of configuration in settings.json and an optional validation script:
.claude/
├── settings.json - Hook configuration (required)
│ └── hooks:
│ └── EventName: - Which event to hook (PreToolUse, PostToolUse, etc.)
│ └── matcher: - Which tools to match (regex pattern)
│ └── command: - What to execute (shell command or script)
└── hooks/ - Hook scripts (optional)
└── script-name.py - Validation/automation logic
Hooks are configured in settings.json with event type, tool matcher, and command:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/validate-bash.py",
"timeout": 60
}
]
}
]
}
}
Scripts receive JSON via stdin and communicate via exit codes:
Scripts can also output JSON for advanced control (permission decisions, additional context, custom output).
Hooks use a three-phase execution system:
Claude Code supports these hook events:
PreToolUse - Execute before a tool runs. Use for validation, blocking dangerous operations, or auto-approving safe actions.
PostToolUse - Execute after a tool completes. Use for formatting code, running linters, sending notifications, or providing feedback to Claude.
UserPromptSubmit - Execute when user submits a prompt. Use for adding contextual information, validating prompt content, or blocking sensitive data.
SessionStart - Execute when Claude Code starts or resumes. Use for loading recent commits, showing current branch, displaying open issues, or initializing session context.
SessionEnd - Execute when a session ends. Use for saving statistics, cleaning temporary files, or logging activity.
Stop/SubagentStop - Execute when Claude attempts to stop. Use for ensuring tasks are complete or forcing continuation if needed.
PreCompact - Execute before conversation history compaction. Use for saving important context or logging stats.
Notification - Execute when Claude Code sends notifications. Use for forwarding to external systems or logging events.
For detailed information on each event type, see references/hook-events.md.
Hooks are configured in settings.json files with this structure:
{
"hooks": {
"EventName": [
{
"matcher": "ToolPattern",
"hooks": [
{
"type": "command",
"command": "path/to/script.py",
"timeout": 60
}
]
}
]
}
}
Key elements:
matcher: Pattern to match tool names (e.g., "Write|Edit", "Bash", "*" for all)command: Shell command or script to executetimeout: Maximum execution time in seconds (default: 60)Use environment variables in commands:
CLAUDE_PROJECT_DIR - Project root directoryCLAUDE_PLUGIN_ROOT - Plugin directory (plugin hooks only)Hook scripts communicate through exit codes:
0 - Success (stdout shown in transcript, or added as context for UserPromptSubmit/SessionStart)2 - Blocking error (stderr shown to Claude for processing)Hooks receive JSON via stdin with session information and event-specific data:
#!/usr/bin/env python3
import json
import sys
input_data = json.load(sys.stdin)
tool_name = input_data.get("tool_name")
tool_input = input_data.get("tool_input", {})
# Hook logic here
sys.exit(0)
For simple hooks, use exit codes. For advanced control, output JSON:
import json
output = {
"decision": "block", # or undefined
"reason": "Explanation",
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow", # "allow", "deny", "ask"
"additionalContext": "Context for Claude"
}
}
print(json.dumps(output))
sys.exit(0)
For detailed hook implementation:
User hooks: ~/.claude/settings.json - Personal preferences, cross-project automation
Project hooks: .claude/settings.json - Team workflows, project-specific validations
Local hooks: .claude/settings.local.json - Local testing, not committed to git
Plugin hooks: plugins/{name}/hooks/hooks.json - Reusable workflows for plugins
Hooks work with MCP tools using the naming pattern mcp__<server>__<tool>:
{
"hooks": {
"PreToolUse": [
{
"matcher": "mcp__github__.*",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/validate-github.py"
}
]
}
]
}
}
To create a hook, follow the "Hook Creation Process" in order, skipping steps only if there is a clear reason they are not applicable.
Skip this step only when the hook's purpose and triggering conditions are already clearly understood.
To create an effective hook, clearly understand concrete examples of when the hook should trigger and what it should do. This understanding can come from either specific use cases or scenarios that need automation.
For example, when building a bash validation hook, relevant considerations include:
For a code formatting hook:
Conclude this step when there is a clear sense of what the hook should accomplish and when it should trigger.
To turn requirements into an effective hook, analyze the use case by:
Example: For blocking dangerous bash commands, the analysis shows:
Example: For auto-formatting code after edits, the analysis shows:
Review references/hook-events.md for event-specific details and references/implementation-guide.md for code patterns.
At this point, determine where the hook configuration should live:
~/.claude/settings.json) - Personal preferences that apply across all projects.claude/settings.json) - Team workflows and project-specific policies.claude/settings.local.json) - Testing and development, not committed to gitplugins/{name}/hooks/hooks.json) - Reusable workflows distributed with pluginsChoose user-level for personal automation (e.g., "always load git context at session start").
Choose project-level for team policies (e.g., "block commits with sensitive data").
Choose local-level for testing hooks before committing to the project.
When implementing the hook, start with the simplest approach that meets requirements.
If the hook just needs to run a command without validation, use inline commands:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "black \"${file_path}\"",
"timeout": 30
}
]
}
]
}
}
If the hook needs to make decisions, create a script:
.claude/hooks/validate-bash.py${CLAUDE_PROJECT_DIR}Reference references/implementation-guide.md for quick patterns and production-ready examples.
Writing Style: Focus on information that would be beneficial and non-obvious to the hook script. Consider security implications, edge cases, and performance optimizations from references/best-practices.md.
Once the hook is configured, test it thoroughly before relying on it:
Test the hook script independently before integrating:
# Create test input
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' | \
.claude/hooks/validate-bash.py
# Check exit code
echo "Exit code: $?"
# Check stderr output
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' | \
.claude/hooks/validate-bash.py 2>&1
Run Claude Code with debug mode to see hook execution:
claude --debug
This shows:
Use transcript mode (Ctrl-R) to see hook progress messages in the conversation timeline.
After deploying the hook, users may encounter edge cases or request improvements. This often happens during real usage when unexpected scenarios arise.
Iteration workflow:
claude --debug to verify improvements