Creates event hooks for Claude Code automation with proper configuration, matchers, input/output handling, and security best practices. Covers all 9 hook types (PreToolUse, PostToolUse, UserPromptSubmit, Notification, Stop, SubagentStop, PreCompact, SessionStart, SessionEnd). Use when building automation, creating hooks, setting up event handlers, or when users mention hooks, automation, event handlers, or tool interception.
Creates event hooks that automate workflows and respond to Claude Code events. Use when building automation, creating hooks, setting up event handlers, or when users mention hooks, automation, event handlers, or tool interception.
/plugin marketplace add outfitter-dev/agents/plugin install agent-kit@outfitterThis skill inherits all available tools. When active, it can use any tool Claude has access to.
EXAMPLES.mdREFERENCE.mdscripts/scaffold-hook.shscripts/test-hook.tsscripts/validate-hook.shCreate event hooks that automate workflows and respond to Claude Code events.
Event hooks are shell commands or scripts that run automatically in response to Claude Code events:
Auto-format Python files after writing:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write(*.py)",
"hooks": [
{
"type": "command",
"command": "black \"$file\""
}
]
}
]
}
}
Add to .claude/settings.json or ~/.claude/settings.json.
Prevent problematic bash commands:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.sh",
"timeout": 5
}
]
}
]
}
}
validate-bash.sh:
#!/usr/bin/env bash
set -euo pipefail
# Read hook input from stdin
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
# Validate command
if echo "$COMMAND" | grep -qE '\brm\s+-rf\s+/'; then
echo "❌ Dangerous command blocked: rm -rf /" >&2
exit 2 # Exit 2 = block and show error to Claude
fi
# Approve
exit 0
{
"hooks": {
"<EventName>": [
{
"matcher": "<ToolPattern>",
"hooks": [
{
"type": "command",
"command": "<shell-command>",
"timeout": 30
}
]
}
]
}
}
Project hooks (.claude/settings.json):
{
"hooks": {
"PostToolUse": [...]
}
}
Personal hooks (~/.claude/settings.json):
{
"hooks": {
"PostToolUse": [...]
}
}
Plugin hooks (plugin/hooks/hooks.json or plugin/.claude-plugin/plugin.json):
{
"hooks": {
"PostToolUse": [...]
}
}
Matchers determine which tool invocations trigger the hook.
{"matcher": "Write"} // Only Write tool
{"matcher": "Edit"} // Only Edit tool
{"matcher": "Bash"} // Only Bash tool
{"matcher": "Edit|Write"} // Edit OR Write
{"matcher": "Notebook.*"} // Any Notebook tool
{"matcher": "Write|Edit|Notebook.*"} // Multiple tools
{"matcher": "*"} // ALL tools
{"matcher": "Write(*.py)"} // Write Python files
{"matcher": "Edit(*.ts)"} // Edit TypeScript files
{"matcher": "Write(*.md)"} // Write Markdown files
{"matcher": "Write|Edit(*.js)"} // Write or Edit JS files
{"matcher": "mcp__memory__.*"} // Any memory MCP tool
{"matcher": "mcp__github__.*"} // Any GitHub MCP tool
{"matcher": "mcp__.*__.*"} // Any MCP tool
Hooks receive JSON on stdin with event context:
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.jsonl",
"cwd": "/current/working/directory",
"hook_event_name": "PreToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "/path/to/file.txt",
"content": "file content"
}
}
#!/usr/bin/env bash
set -euo pipefail
# Read and parse JSON input
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
# Use the data
echo "Tool: $TOOL_NAME"
echo "File: $FILE_PATH"
#!/usr/bin/env bun
import { stdin } from "process";
// Read stdin
const chunks: Buffer[] = [];
for await (const chunk of stdin) {
chunks.push(chunk);
}
const input = JSON.parse(Buffer.concat(chunks).toString());
// Access data
const toolName = input.tool_name;
const filePath = input.tool_input?.file_path;
console.log(`Tool: ${toolName}`);
console.log(`File: ${filePath}`);
#!/usr/bin/env bash
# Success (continue)
exit 0
# Blocking error (show to Claude)
echo "Error: validation failed" >&2
exit 2
# Non-blocking error (show to user)
echo "Warning: check failed" >&2
exit 1
Behavior:
{
"continue": true,
"stopReason": "Optional message",
"suppressOutput": false,
"systemMessage": "Warning message",
"decision": "block",
"reason": "Explanation",
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Dangerous operation",
"additionalContext": "Context for Claude"
}
}
Runs before tool executes. Can block or approve operations.
Common uses:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [{
"type": "command",
"command": "./.claude/hooks/check-file-policy.sh"
}]
}
]
}
}
Runs after tool completes successfully.
Common uses:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit(*.ts)",
"hooks": [{
"type": "command",
"command": "biome check --write \"$file\""
}]
}
]
}
}
Runs when user submits a prompt.
Common uses:
{
"hooks": {
"UserPromptSubmit": [
{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "./.claude/hooks/add-context.sh"
}]
}
]
}
}
Runs when Claude sends notifications.
Common uses:
{
"hooks": {
"Notification": [
{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "./.claude/hooks/log-notification.sh"
}]
}
]
}
}
Runs when main Claude agent finishes responding.
Common uses:
{
"hooks": {
"Stop": [
{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "./.claude/hooks/on-completion.sh"
}]
}
]
}
}
Runs when a subagent finishes.
Common uses:
{
"hooks": {
"SubagentStop": [
{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "./.claude/hooks/on-subagent-done.sh"
}]
}
]
}
}
Runs before conversation compacts.
Matchers:
manual: User-triggered (/compact)auto: Automatic compact{
"hooks": {
"PreCompact": [
{
"matcher": "manual",
"hooks": [{
"type": "command",
"command": "./.claude/hooks/before-compact.sh"
}]
}
]
}
}
Runs when session starts or resumes.
Matchers:
startup: Claude Code startsresume: Session resumes (--resume)clear: After /clearcompact: After compact{
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [{
"type": "command",
"command": "echo 'Welcome!' && git status"
}]
}
]
}
}
Runs when session ends.
Reasons:
clear: User ran /clearlogout: User logged outprompt_input_exit: Exited during promptother: Other reasons{
"hooks": {
"SessionEnd": [
{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "./.claude/hooks/cleanup.sh"
}]
}
]
}
}
#!/usr/bin/env bash
set -euo pipefail
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
# Check for path traversal
if echo "$FILE_PATH" | grep -q '\.\.'; then
echo "❌ Path traversal detected" >&2
exit 2
fi
# Check for sensitive paths
if echo "$FILE_PATH" | grep -qE '^/etc/|^/root/|\.env$'; then
echo "❌ Sensitive path blocked" >&2
exit 2
fi
# ❌ WRONG - vulnerable to injection
rm $FILE_PATH
# ✅ CORRECT - properly quoted
rm "$FILE_PATH"
{
"hooks": {
"PostToolUse": [{
"hooks": [{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/format.sh"
}]
}]
}
}
{
"hooks": [{
"type": "command",
"command": "./slow-operation.sh",
"timeout": 30
}]
}
#!/usr/bin/env bash
set -euo pipefail
# Always validate input exists
if ! command -v jq &>/dev/null; then
echo "Error: jq not installed" >&2
exit 1
fi
# Catch errors
if ! INPUT=$(cat 2>&1); then
echo "Error: Failed to read stdin" >&2
exit 1
fi
See REFERENCE.md for:
See EXAMPLES.md for:
# Create hook with template
./scripts/scaffold-hook.sh format-typescript
# Validate hook configuration
./scripts/validate-hook.sh .claude/settings.json
# Test hook script with sample input
./scripts/test-hook.ts .claude/hooks/my-hook.sh
claude --debug
Use transcript mode (Ctrl+R) to see hook execution and output.
# Create sample input
cat > /tmp/hook-input.json << 'EOF'
{
"tool_name": "Write",
"tool_input": {
"file_path": "test.ts",
"content": "console.log('test');"
}
}
EOF
# Test hook
cat /tmp/hook-input.json | ./.claude/hooks/my-hook.sh
Hook not firing:
Permission errors:
chmod +x script.sh$CLAUDE_PROJECT_DIR is setTimeout errors:
Available in hook scripts:
$CLAUDE_PROJECT_DIR: Project root directory$file: File path (PostToolUse hooks)This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
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.