Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Guidance for developing lifecycle hooks for Claude Code plugins from the official Anthropic repository.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
SKILL.md
1---2name: Hook Development3description: 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.4version: 0.1.05---67# Hook Development for Claude Code Plugins89## Overview1011Hooks 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.1213**Key capabilities:**14- Validate tool calls before execution (PreToolUse)15- React to tool results (PostToolUse)16- Enforce completion standards (Stop, SubagentStop)17- Load project context (SessionStart)18- Automate workflows across the development lifecycle1920## Hook Types2122### Prompt-Based Hooks (Recommended)2324Use LLM-driven decision making for context-aware validation:2526```json27{28"type": "prompt",29"prompt": "Evaluate if this tool use is appropriate: $TOOL_INPUT",30"timeout": 3031}32```3334**Supported events:** Stop, SubagentStop, UserPromptSubmit, PreToolUse3536**Benefits:**37- Context-aware decisions based on natural language reasoning38- Flexible evaluation logic without bash scripting39- Better edge case handling40- Easier to maintain and extend4142### Command Hooks4344Execute bash commands for deterministic checks:4546```json47{48"type": "command",49"command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/validate.sh",50"timeout": 6051}52```5354**Use for:**55- Fast deterministic validations56- File system operations57- External tool integrations58- Performance-critical checks5960## Hook Configuration Formats6162### Plugin hooks.json Format6364**For plugin hooks** in `hooks/hooks.json`, use wrapper format:6566```json67{68"description": "Brief explanation of hooks (optional)",69"hooks": {70"PreToolUse": [...],71"Stop": [...],72"SessionStart": [...]73}74}75```7677**Key points:**78- `description` field is optional79- `hooks` field is required wrapper containing actual hook events80- This is the **plugin-specific format**8182**Example:**83```json84{85"description": "Validation hooks for code quality",86"hooks": {87"PreToolUse": [88{89"matcher": "Write",90"hooks": [91{92"type": "command",93"command": "${CLAUDE_PLUGIN_ROOT}/hooks/validate.sh"94}95]96}97]98}99}100```101102### Settings Format (Direct)103104**For user settings** in `.claude/settings.json`, use direct format:105106```json107{108"PreToolUse": [...],109"Stop": [...],110"SessionStart": [...]111}112```113114**Key points:**115- No wrapper - events directly at top level116- No description field117- This is the **settings format**118119**Important:** The examples below show the hook event structure that goes inside either format. For plugin hooks.json, wrap these in `{"hooks": {...}}`.120121## Hook Events122123### PreToolUse124125Execute before any tool runs. Use to approve, deny, or modify tool calls.126127**Example (prompt-based):**128```json129{130"PreToolUse": [131{132"matcher": "Write|Edit",133"hooks": [134{135"type": "prompt",136"prompt": "Validate file write safety. Check: system paths, credentials, path traversal, sensitive content. Return 'approve' or 'deny'."137}138]139}140]141}142```143144**Output for PreToolUse:**145```json146{147"hookSpecificOutput": {148"permissionDecision": "allow|deny|ask",149"updatedInput": {"field": "modified_value"}150},151"systemMessage": "Explanation for Claude"152}153```154155### PostToolUse156157Execute after tool completes. Use to react to results, provide feedback, or log.158159**Example:**160```json161{162"PostToolUse": [163{164"matcher": "Edit",165"hooks": [166{167"type": "prompt",168"prompt": "Analyze edit result for potential issues: syntax errors, security vulnerabilities, breaking changes. Provide feedback."169}170]171}172]173}174```175176**Output behavior:**177- Exit 0: stdout shown in transcript178- Exit 2: stderr fed back to Claude179- systemMessage included in context180181### Stop182183Execute when main agent considers stopping. Use to validate completeness.184185**Example:**186```json187{188"Stop": [189{190"matcher": "*",191"hooks": [192{193"type": "prompt",194"prompt": "Verify task completion: tests run, build succeeded, questions answered. Return 'approve' to stop or 'block' with reason to continue."195}196]197}198]199}200```201202**Decision output:**203```json204{205"decision": "approve|block",206"reason": "Explanation",207"systemMessage": "Additional context"208}209```210211### SubagentStop212213Execute when subagent considers stopping. Use to ensure subagent completed its task.214215Similar to Stop hook, but for subagents.216217### UserPromptSubmit218219Execute when user submits a prompt. Use to add context, validate, or block prompts.220221**Example:**222```json223{224"UserPromptSubmit": [225{226"matcher": "*",227"hooks": [228{229"type": "prompt",230"prompt": "Check if prompt requires security guidance. If discussing auth, permissions, or API security, return relevant warnings."231}232]233}234]235}236```237238### SessionStart239240Execute when Claude Code session begins. Use to load context and set environment.241242**Example:**243```json244{245"SessionStart": [246{247"matcher": "*",248"hooks": [249{250"type": "command",251"command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/load-context.sh"252}253]254}255]256}257```258259**Special capability:** Persist environment variables using `$CLAUDE_ENV_FILE`:260```bash261echo "export PROJECT_TYPE=nodejs" >> "$CLAUDE_ENV_FILE"262```263264See `examples/load-context.sh` for complete example.265266### SessionEnd267268Execute when session ends. Use for cleanup, logging, and state preservation.269270### PreCompact271272Execute before context compaction. Use to add critical information to preserve.273274### Notification275276Execute when Claude sends notifications. Use to react to user notifications.277278## Hook Output Format279280### Standard Output (All Hooks)281282```json283{284"continue": true,285"suppressOutput": false,286"systemMessage": "Message for Claude"287}288```289290- `continue`: If false, halt processing (default true)291- `suppressOutput`: Hide output from transcript (default false)292- `systemMessage`: Message shown to Claude293294### Exit Codes295296- `0` - Success (stdout shown in transcript)297- `2` - Blocking error (stderr fed back to Claude)298- Other - Non-blocking error299300## Hook Input Format301302All hooks receive JSON via stdin with common fields:303304```json305{306"session_id": "abc123",307"transcript_path": "/path/to/transcript.txt",308"cwd": "/current/working/dir",309"permission_mode": "ask|allow",310"hook_event_name": "PreToolUse"311}312```313314**Event-specific fields:**315316- **PreToolUse/PostToolUse:** `tool_name`, `tool_input`, `tool_result`317- **UserPromptSubmit:** `user_prompt`318- **Stop/SubagentStop:** `reason`319320Access fields in prompts using `$TOOL_INPUT`, `$TOOL_RESULT`, `$USER_PROMPT`, etc.321322## Environment Variables323324Available in all command hooks:325326- `$CLAUDE_PROJECT_DIR` - Project root path327- `$CLAUDE_PLUGIN_ROOT` - Plugin directory (use for portable paths)328- `$CLAUDE_ENV_FILE` - SessionStart only: persist env vars here329- `$CLAUDE_CODE_REMOTE` - Set if running in remote context330331**Always use ${CLAUDE_PLUGIN_ROOT} in hook commands for portability:**332333```json334{335"type": "command",336"command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/validate.sh"337}338```339340## Plugin Hook Configuration341342In plugins, define hooks in `hooks/hooks.json`:343344```json345{346"PreToolUse": [347{348"matcher": "Write|Edit",349"hooks": [350{351"type": "prompt",352"prompt": "Validate file write safety"353}354]355}356],357"Stop": [358{359"matcher": "*",360"hooks": [361{362"type": "prompt",363"prompt": "Verify task completion"364}365]366}367],368"SessionStart": [369{370"matcher": "*",371"hooks": [372{373"type": "command",374"command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/load-context.sh",375"timeout": 10376}377]378}379]380}381```382383Plugin hooks merge with user's hooks and run in parallel.384385## Matchers386387### Tool Name Matching388389**Exact match:**390```json391"matcher": "Write"392```393394**Multiple tools:**395```json396"matcher": "Read|Write|Edit"397```398399**Wildcard (all tools):**400```json401"matcher": "*"402```403404**Regex patterns:**405```json406"matcher": "mcp__.*__delete.*" // All MCP delete tools407```408409**Note:** Matchers are case-sensitive.410411### Common Patterns412413```json414// All MCP tools415"matcher": "mcp__.*"416417// Specific plugin's MCP tools418"matcher": "mcp__plugin_asana_.*"419420// All file operations421"matcher": "Read|Write|Edit"422423// Bash commands only424"matcher": "Bash"425```426427## Security Best Practices428429### Input Validation430431Always validate inputs in command hooks:432433```bash434#!/bin/bash435set -euo pipefail436437input=$(cat)438tool_name=$(echo "$input" | jq -r '.tool_name')439440# Validate tool name format441if [[ ! "$tool_name" =~ ^[a-zA-Z0-9_]+$ ]]; then442echo '{"decision": "deny", "reason": "Invalid tool name"}' >&2443exit 2444fi445```446447### Path Safety448449Check for path traversal and sensitive files:450451```bash452file_path=$(echo "$input" | jq -r '.tool_input.file_path')453454# Deny path traversal455if [[ "$file_path" == *".."* ]]; then456echo '{"decision": "deny", "reason": "Path traversal detected"}' >&2457exit 2458fi459460# Deny sensitive files461if [[ "$file_path" == *".env"* ]]; then462echo '{"decision": "deny", "reason": "Sensitive file"}' >&2463exit 2464fi465```466467See `examples/validate-write.sh` and `examples/validate-bash.sh` for complete examples.468469### Quote All Variables470471```bash472# GOOD: Quoted473echo "$file_path"474cd "$CLAUDE_PROJECT_DIR"475476# BAD: Unquoted (injection risk)477echo $file_path478cd $CLAUDE_PROJECT_DIR479```480481### Set Appropriate Timeouts482483```json484{485"type": "command",486"command": "bash script.sh",487"timeout": 10488}489```490491**Defaults:** Command hooks (60s), Prompt hooks (30s)492493## Performance Considerations494495### Parallel Execution496497All matching hooks run **in parallel**:498499```json500{501"PreToolUse": [502{503"matcher": "Write",504"hooks": [505{"type": "command", "command": "check1.sh"}, // Parallel506{"type": "command", "command": "check2.sh"}, // Parallel507{"type": "prompt", "prompt": "Validate..."} // Parallel508]509}510]511}512```513514**Design implications:**515- Hooks don't see each other's output516- Non-deterministic ordering517- Design for independence518519### Optimization5205211. Use command hooks for quick deterministic checks5222. Use prompt hooks for complex reasoning5233. Cache validation results in temp files5244. Minimize I/O in hot paths525526## Temporarily Active Hooks527528Create hooks that activate conditionally by checking for a flag file or configuration:529530**Pattern: Flag file activation**531```bash532#!/bin/bash533# Only active when flag file exists534FLAG_FILE="$CLAUDE_PROJECT_DIR/.enable-strict-validation"535536if [ ! -f "$FLAG_FILE" ]; then537# Flag not present, skip validation538exit 0539fi540541# Flag present, run validation542input=$(cat)543# ... validation logic ...544```545546**Pattern: Configuration-based activation**547```bash548#!/bin/bash549# Check configuration for activation550CONFIG_FILE="$CLAUDE_PROJECT_DIR/.claude/plugin-config.json"551552if [ -f "$CONFIG_FILE" ]; then553enabled=$(jq -r '.strictMode // false' "$CONFIG_FILE")554if [ "$enabled" != "true" ]; then555exit 0 # Not enabled, skip556fi557fi558559# Enabled, run hook logic560input=$(cat)561# ... hook logic ...562```563564**Use cases:**565- Enable strict validation only when needed566- Temporary debugging hooks567- Project-specific hook behavior568- Feature flags for hooks569570**Best practice:** Document activation mechanism in plugin README so users know how to enable/disable temporary hooks.571572## Hook Lifecycle and Limitations573574### Hooks Load at Session Start575576**Important:** Hooks are loaded when Claude Code session starts. Changes to hook configuration require restarting Claude Code.577578**Cannot hot-swap hooks:**579- Editing `hooks/hooks.json` won't affect current session580- Adding new hook scripts won't be recognized581- Changing hook commands/prompts won't update582- Must restart Claude Code: exit and run `claude` again583584**To test hook changes:**5851. Edit hook configuration or scripts5862. Exit Claude Code session5873. Restart: `claude` or `cc`5884. New hook configuration loads5895. Test hooks with `claude --debug`590591### Hook Validation at Startup592593Hooks are validated when Claude Code starts:594- Invalid JSON in hooks.json causes loading failure595- Missing scripts cause warnings596- Syntax errors reported in debug mode597598Use `/hooks` command to review loaded hooks in current session.599600## Debugging Hooks601602### Enable Debug Mode603604```bash605claude --debug606```607608Look for hook registration, execution logs, input/output JSON, and timing information.609610### Test Hook Scripts611612Test command hooks directly:613614```bash615echo '{"tool_name": "Write", "tool_input": {"file_path": "/test"}}' | \616bash ${CLAUDE_PLUGIN_ROOT}/scripts/validate.sh617618echo "Exit code: $?"619```620621### Validate JSON Output622623Ensure hooks output valid JSON:624625```bash626output=$(./your-hook.sh < test-input.json)627echo "$output" | jq .628```629630## Quick Reference631632### Hook Events Summary633634| Event | When | Use For |635|-------|------|---------|636| PreToolUse | Before tool | Validation, modification |637| PostToolUse | After tool | Feedback, logging |638| UserPromptSubmit | User input | Context, validation |639| Stop | Agent stopping | Completeness check |640| SubagentStop | Subagent done | Task validation |641| SessionStart | Session begins | Context loading |642| SessionEnd | Session ends | Cleanup, logging |643| PreCompact | Before compact | Preserve context |644| Notification | User notified | Logging, reactions |645646### Best Practices647648**DO:**649- ✅ Use prompt-based hooks for complex logic650- ✅ Use ${CLAUDE_PLUGIN_ROOT} for portability651- ✅ Validate all inputs in command hooks652- ✅ Quote all bash variables653- ✅ Set appropriate timeouts654- ✅ Return structured JSON output655- ✅ Test hooks thoroughly656657**DON'T:**658- ❌ Use hardcoded paths659- ❌ Trust user input without validation660- ❌ Create long-running hooks661- ❌ Rely on hook execution order662- ❌ Modify global state unpredictably663- ❌ Log sensitive information664665## Additional Resources666667### Reference Files668669For detailed patterns and advanced techniques, consult:670671- **`references/patterns.md`** - Common hook patterns (8+ proven patterns)672- **`references/migration.md`** - Migrating from basic to advanced hooks673- **`references/advanced.md`** - Advanced use cases and techniques674675### Example Hook Scripts676677Working examples in `examples/`:678679- **`validate-write.sh`** - File write validation example680- **`validate-bash.sh`** - Bash command validation example681- **`load-context.sh`** - SessionStart context loading example682683### Utility Scripts684685Development tools in `scripts/`:686687- **`validate-hook-schema.sh`** - Validate hooks.json structure and syntax688- **`test-hook.sh`** - Test hooks with sample input before deployment689- **`hook-linter.sh`** - Check hook scripts for common issues and best practices690691### External Resources692693- **Official Docs**: https://docs.claude.com/en/docs/claude-code/hooks694- **Examples**: See security-guidance plugin in marketplace695- **Testing**: Use `claude --debug` for detailed logs696- **Validation**: Use `jq` to validate hook JSON output697698## Implementation Workflow699700To implement hooks in a plugin:7017021. Identify events to hook into (PreToolUse, Stop, SessionStart, etc.)7032. Decide between prompt-based (flexible) or command (deterministic) hooks7043. Write hook configuration in `hooks/hooks.json`7054. For command hooks, create hook scripts7065. Use ${CLAUDE_PLUGIN_ROOT} for all file references7076. Validate configuration with `scripts/validate-hook-schema.sh hooks/hooks.json`7087. Test hooks with `scripts/test-hook.sh` before deployment7098. Test in Claude Code with `claude --debug`7109. Document hooks in plugin README711712Focus on prompt-based hooks for most use cases. Reserve command hooks for performance-critical or deterministic checks.713