This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.
Hooks are event-driven automation scripts that execute in response to Claude Code events. Use hooks to validate operations, enforce policies, add context, and integrate external tools into workflows.
Key capabilities:
Use LLM-driven decision making for context-aware validation:
{
"type": "prompt",
"prompt": "Evaluate if this tool use is appropriate: $TOOL_INPUT",
"timeout": 30
}
Supported events: Stop, SubagentStop, UserPromptSubmit, PreToolUse
Execute bash commands for deterministic checks:
{
"type": "command",
"command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/validate.sh",
"timeout": 60
}
Use for:
For plugin hooks in hooks/hooks.json, use wrapper format:
{
"description": "Brief explanation of hooks (optional)",
"hooks": {
"PreToolUse": [...],
"Stop": [...],
"SessionStart": [...]
}
}
Key points:
description field is optionalhooks field is required wrapper containing actual hook eventsExample:
{
"description": "Validation hooks for code quality",
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/validate.sh"
}
]
}
]
}
}
For user settings in .claude/settings.json, use direct format:
{
"PreToolUse": [...],
"Stop": [...],
"SessionStart": [...]
}
Key points:
Important: The examples below show the hook event structure that goes inside either format. For plugin hooks.json, wrap these in {"hooks": {...}}.
Execute before any tool runs. Use to approve, deny, or modify tool calls.
Example (prompt-based):
{
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "prompt",
"prompt": "Validate file write safety. Check: system paths, credentials, path traversal, sensitive content. Return 'approve' or 'deny'."
}
]
}
]
}
Output for PreToolUse:
{
"hookSpecificOutput": {
"permissionDecision": "allow|deny|ask",
"updatedInput": { "field": "modified_value" }
},
"systemMessage": "Explanation for Claude"
}
Execute after tool completes. Use to react to results, provide feedback, or log.
Example:
{
"PostToolUse": [
{
"matcher": "Edit",
"hooks": [
{
"type": "prompt",
"prompt": "Analyze edit result for potential issues: syntax errors, security vulnerabilities, breaking changes. Provide feedback."
}
]
}
]
}
Output behavior:
Execute when main agent considers stopping. Use to validate completeness.
Example:
{
"Stop": [
{
"matcher": "*",
"hooks": [
{
"type": "prompt",
"prompt": "Verify task completion: tests run, build succeeded, questions answered. Return 'approve' to stop or 'block' with reason to continue."
}
]
}
]
}
Decision output (depends on hook type):
For command-based hooks (type: "command" - bash scripts):
{"decision": "approve"} // allow stop, exit 0
{"decision": "block", "reason": "..."} // block stop, exit 0
WARNING: Exit code 2 + stderr does NOT work reliably. Testing confirmed it's treated as plain text, not a blocking signal. Always use JSON with exit 0.
For prompt-based hooks (type: "prompt" - LLM evaluation):
{"ok": true}
{"ok": false, "reason": "Explanation of why work should continue"}
CRITICAL:
type: "command" → Use {"decision": "block"} with exit 0 (exit 2 is unreliable!)type: "prompt" → Use {"ok": boolean} (LLM responds to prompt asking for this format)Execute when subagent considers stopping. Use to ensure subagent completed its task.
Key differences from Stop:
Important: PostToolUse hooks DO fire for subagent tool calls, so state tracking works across both main agent and subagents.
Example:
{
"SubagentStop": [
{
"matcher": "*",
"hooks": [
{
"type": "prompt",
"prompt": "Verify subagent completed its assigned task. Check if code was modified and appropriate verification ran. Return {\"ok\": true} or {\"ok\": false, \"reason\": \"...\"}",
"timeout": 45
}
]
}
]
}
Execute when user submits a prompt. Use to add context, validate, or block prompts.
Example config:
{
"UserPromptSubmit": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/my-hook.sh"
}
]
}
]
}
Command-based output format (REQUIRED schema):
{
"continue": true,
"suppressOutput": false,
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "Context string to add to conversation"
}
}
CRITICAL: hookEventName: "UserPromptSubmit" is required when using hookSpecificOutput. Omitting it causes validation error.
To block a prompt:
{
"decision": "block",
"reason": "Explanation shown to user (not added to context)"
}
Execute when Claude Code session begins. Use to load context and set environment.
Example:
{
"SessionStart": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/load-context.sh"
}
]
}
]
}
Special capability: Persist environment variables using $CLAUDE_ENV_FILE:
echo "export PROJECT_TYPE=nodejs" >> "$CLAUDE_ENV_FILE"
See examples/load-context.sh for complete example.
Execute when session ends. Use for cleanup, logging, and state preservation.
Execute before context compaction. Use to add critical information to preserve.
Execute when Claude sends notifications. Use to react to user notifications.
{
"continue": true,
"suppressOutput": false,
"systemMessage": "Message for Claude"
}
continue: If false, halt processing (default true)suppressOutput: Hide output from transcript (default false)systemMessage: Message shown to Claude0 - Success (stdout JSON parsed and processed)2 - UNRELIABLE (stderr shown but does NOT block - use JSON instead)Important: Always use exit 0 with JSON output. Exit code 2 does not reliably block.
All hooks receive JSON via stdin with common and event-specific fields. Use environment variables like $CLAUDE_PLUGIN_ROOT for portability.
See: references/hook-input-format.md for complete input schema, event-specific fields, and environment variables.
Define hooks in hooks/hooks.json with event types (PreToolUse, Stop, SessionStart), matchers, and hook configurations. Plugin hooks merge with user hooks and run in parallel.
See: references/patterns.md for complete plugin configuration examples.
Matchers are case-sensitive and support exact match ("Write"), multiple tools ("Read|Write|Edit"), wildcard ("*"), and regex patterns ("mcp__.*__delete.*").
See: references/patterns.md for common matcher patterns (MCP tools, file operations, specific plugins).
Always validate inputs, check for path traversal, quote all bash variables, and set appropriate timeouts.
See: references/security.md for detailed patterns including input validation, path safety checks, variable quoting, and timeout configuration.
All matching hooks run in parallel. Design for independence and optimize by using command hooks for deterministic checks and prompt hooks for complex reasoning.
See: references/performance.md for parallel execution patterns, design implications, and optimization strategies.
Create hooks that activate conditionally using flag files or configuration checks. Useful for enabling strict validation only when needed, temporary debugging, or feature flags.
See: references/advanced.md for flag file activation patterns, configuration-based activation, and best practices.
Skills like iterating-to-completion document max_iterations: 10, but these are guidance - Claude can rationalize around them. Stop hooks provide deterministic enforcement.
State file tracks iterations; Stop hook blocks exit until limit reached:
#!/bin/bash
# iteration-counter-stop.sh
set -euo pipefail
STATE_FILE="${CLAUDE_PROJECT_DIR}/.claude/iteration-state.json"
# Read current state
if [ -f "$STATE_FILE" ]; then
iteration=$(jq -r '.iteration // 0' "$STATE_FILE")
max_iterations=$(jq -r '.max_iterations // 10' "$STATE_FILE")
else
# No state file = not in iteration mode, allow exit
echo '{"decision": "approve"}'
exit 0
fi
# Check if limit reached
if [ "$iteration" -ge "$max_iterations" ]; then
rm -f "$STATE_FILE" # Clean up, allow exit
echo '{"decision": "approve"}'
exit 0
fi
# Increment and block exit
iteration=$((iteration + 1))
jq --argjson iter "$iteration" '.iteration = $iter' "$STATE_FILE" > "${STATE_FILE}.tmp"
mv "${STATE_FILE}.tmp" "$STATE_FILE"
# Block with continuation reason (command hooks use "decision", not "ok")
cat <<EOF
{
"decision": "block",
"reason": "Iteration $iteration of $max_iterations. Continue working on the task."
}
EOF
exit 0
Create state file to activate the loop:
echo '{"iteration": 0, "max_iterations": 10, "task": "description"}' > .claude/iteration-state.json
{
"Stop": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "bash ${CLAUDE_PROJECT_DIR}/.claude/hooks/iteration-counter-stop.sh",
"timeout": 5
}
]
}
]
}
Hybrid layers: Skill (guidance: max_iterations: 10) → State file (tracking: .claude/iteration-state.json) → Stop hook (enforcement: JSON {"decision": "block"} with exit 0) → Scratchpad (context: scratchpad-{task}.md).
Activation: State file presence enables iteration mode. No file = hook inactive. Removing file allows exit. Compatible with prompt-based hooks.
Source: Community research on autonomous loops (Ralph Wiggum, Continuous-Claude-v3, claude-code-hooks-mastery).
Hooks load at session start and require restart to pick up changes. Use claude --debug for troubleshooting and test scripts directly before deployment.
See: references/lifecycle-debugging.md for lifecycle limitations, hot-swap constraints, debugging techniques, and testing patterns.
Hook Events: PreToolUse (validation), PostToolUse (feedback), Stop/SubagentStop (completeness), UserPromptSubmit (context), SessionStart (loading), SessionEnd (cleanup), PreCompact (preservation), Notification (reactions).
Best Practices: Use prompt-based hooks for complex logic, ${CLAUDE_PLUGIN_ROOT} for portability, validate inputs, quote variables, set timeouts, test thoroughly. Avoid hardcoded paths, long-running operations, execution order dependencies.
gateway-claude - Routes hook development tasks ("create a hook", "add a PreToolUse hook", "use ${CLAUDE_PLUGIN_ROOT}") to this skillNone - This skill is self-contained documentation and can be used immediately.
None - This skill provides reference documentation and does not invoke other skills.
None - Standalone reference skill for hook development.
Reference files: See references/ directory for detailed patterns, security best practices, performance optimization, lifecycle management, and migration guides.
External: Official documentation at https://docs.claude.com/en/docs/claude-code/hooks. Implementation workflow in references/patterns.md.