| name | hook-development |
| description | Guide for creating event-driven hooks that validate, log, or modify Claude's behavior. Use when the user asks to "create a hook", "add PreToolUse hook", "validate tool use", "hook events", "hooks.json", or mentions hook events (PreToolUse, PostToolUse, Stop). |
| version | 0.6.2 |
Hook Development
Guide for creating event-driven hooks in hooks/hooks.json.
hooks.json Format
{
"description": "What these hooks do",
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/validate.js",
"timeout": 30
}
]
}
]
}
}
Hook Types
Command hooks execute a script (see format above). Prompt-based hooks let Claude evaluate conditions — useful for semantic checks hard to script:
{
"type": "prompt",
"prompt": "Check if this file modification follows coding standards. If it violates any standards, explain why and set permissionDecision to 'deny'."
}
Hook Events
| Event | When It Fires |
|---|
SessionStart | Session begins or resumes |
UserPromptSubmit | User submits a prompt |
PreToolUse | Before tool execution |
PostToolUse | After tool succeeds |
Stop | Claude finishes responding |
SubagentStart | When spawning a subagent |
SubagentStop | When subagent finishes |
PreCompact | Before context compaction |
SessionEnd | Session terminates |
Notification | Claude sends notifications |
Setup | With --init or --maintenance flags |
Environment Variables
| Variable | Scope | Description |
|---|
${CLAUDE_PLUGIN_ROOT} | Plugin hooks | Absolute path to plugin |
"$CLAUDE_PROJECT_DIR" | Project hooks | Project root (quoted) |
Critical: Plugin hooks use ${CLAUDE_PLUGIN_ROOT} with braces. Project hooks use "$CLAUDE_PROJECT_DIR" with quotes.
Exit Codes
| Code | Meaning |
|---|
| 0 | Success — allow action |
| 2 | Block — deny action, show stderr |
| Other | Non-blocking error |
Common Matchers
Write|Edit — File modifications
Bash — Shell commands
* or "" — All tools
mcp__.* — All MCP tools
Temporarily Active Hooks
Hooks can be enabled/disabled via .local.md config:
const config = parseLocalMd(pluginRoot);
if (!config.hooks?.validation_enabled) {
process.exit(0);
}
See /claude-plugin-dev:plugin-settings for configuration patterns.
JSON Output (PreToolUse)
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "Explanation"
}
}
Permission values: allow, deny, ask
Hook Script Templates
Bash Hook (Preferred for validation/logging)
#!/bin/bash
set -e
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // "unknown"')
TOOL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}')
exit 0
Node.js Hook (Cross-Platform Shebang Required)
Never use #!/usr/bin/env node — it fails on macOS when Node is installed via version managers (nvm, fnm, volta). Use this polyglot preamble instead:
#!/bin/sh
":"
":"
":"
'use strict';
let input = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
try {
const data = JSON.parse(input || '{}');
console.log(JSON.stringify({ continue: true }));
} catch (err) {
process.exit(0);
}
});
For detailed patterns, bash examples, and best practices: read references/patterns.md.