Claude-skill-registry hooks-reference
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/hooks-reference" ~/.claude/skills/majiayu000-claude-skill-registry-hooks-reference && rm -rf "$T"
manifest:
skills/data/hooks-reference/SKILL.mdsource content
Hooks Reference Skill
This skill provides comprehensive guidance for implementing Claude Code hooks - event handlers that automate validation, context loading, and workflow enforcement.
Hook Events Overview
| Event | When Triggered | Use Cases |
|---|---|---|
| Before tool executes | Validate, block, modify |
| After tool completes | Audit, react, verify |
| Permission dialog shown | Auto-allow, auto-deny |
| Claude finishes | Force continue, verify |
| Subagent finishes | Verify task complete |
| Session begins | Load context, setup |
| Session ends | Cleanup, save state |
| Prompt submitted | Validate, add context |
| Before compaction | Preserve info |
| Notification sent | Alert, log |
hooks.json Structure
Basic structure:
{ "description": "What these hooks do", "hooks": { "EventName": [ { "matcher": "Pattern", "hooks": [ { "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/scripts/handler.sh", "timeout": 30 } ] } ] } }
Matcher Patterns
For tool events (PreToolUse, PostToolUse, PermissionRequest):
- Match exact tool name"Write"
- Match multiple tools (regex)"Write|Edit"
- Regex pattern"Notebook.*"
or"*"
- Match all tools""
For SessionStart:
- Initial startup"startup"
- From --resume, --continue, /resume"resume"
- From /clear"clear"
- From auto/manual compact"compact"
For Notification:
- Permission requests"permission_prompt"
- Claude waiting for input"idle_prompt"
Hook Types
Command Hooks (type: "command")
Execute a bash script:
{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/scripts/validate.sh", "timeout": 30 }
Prompt Hooks (type: "prompt")
LLM-based evaluation (Stop, SubagentStop only):
{ "type": "prompt", "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete.", "timeout": 30 }
Exit Codes
| Exit Code | Meaning | Behavior |
|---|---|---|
| 0 | Success | Action proceeds, stdout to user (verbose) |
| 2 | Block | Action blocked, stderr shown to Claude |
| Other | Error | Non-blocking, stderr to user (verbose) |
Hook Input (stdin JSON)
Common fields:
{ "session_id": "abc123", "transcript_path": "/path/to/transcript.jsonl", "cwd": "/current/working/directory", "permission_mode": "default", "hook_event_name": "PreToolUse" }
PreToolUse specific:
{ "tool_name": "Write", "tool_input": { "file_path": "/path/to/file.txt", "content": "file content" }, "tool_use_id": "toolu_01ABC..." }
SessionStart specific:
{ "source": "startup" }
Stop specific:
{ "stop_hook_active": false }
Advanced JSON Output
Return structured decisions via stdout (exit code 0):
PreToolUse Decision Control
{ "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "allow", "permissionDecisionReason": "Auto-approved documentation file", "updatedInput": { "field_to_modify": "new value" } } }
Decision values:
"allow", "deny", "ask"
Stop Decision Control
{ "decision": "block", "reason": "Not all tasks complete - still need to run tests" }
UserPromptSubmit Context
{ "hookSpecificOutput": { "hookEventName": "UserPromptSubmit", "additionalContext": "Current time: 2024-01-15 10:30:00" } }
SessionStart Context
{ "hookSpecificOutput": { "hookEventName": "SessionStart", "additionalContext": "Project context loaded..." } }
Example Hooks
1. Validate File Writes (PreToolUse)
hooks/hooks.json:
{ "description": "Validate file write operations", "hooks": { "PreToolUse": [ { "matcher": "Write|Edit", "hooks": [ { "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/scripts/validate-write.sh", "timeout": 30 } ] } ] } }
scripts/validate-write.sh:
#!/usr/bin/env bash INPUT=$(cat) FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.filePath // empty') # Block writes to sensitive files if [[ "$FILE_PATH" == *.env* ]] || [[ "$FILE_PATH" == *secret* ]]; then echo "Cannot write to sensitive files: $FILE_PATH" >&2 exit 2 fi # Block writes outside project if [[ ! "$FILE_PATH" == "$CLAUDE_PROJECT_DIR"* ]]; then echo "Cannot write outside project directory" >&2 exit 2 fi exit 0
2. Block Dangerous Commands (PreToolUse)
hooks/hooks.json:
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/scripts/validate-bash.sh" } ] } ] } }
scripts/validate-bash.sh:
#!/usr/bin/env bash INPUT=$(cat) COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty') # Block destructive commands DANGEROUS_PATTERNS=( "rm -rf /" "rm -rf ~" ":(){ :|:& };:" "> /dev/sda" ) for pattern in "${DANGEROUS_PATTERNS[@]}"; do if [[ "$COMMAND" == *"$pattern"* ]]; then echo "Blocked dangerous command pattern: $pattern" >&2 exit 2 fi done exit 0
3. Load Project Context (SessionStart)
hooks/hooks.json:
{ "hooks": { "SessionStart": [ { "matcher": "startup|resume", "hooks": [ { "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/scripts/load-context.sh" } ] } ] } }
scripts/load-context.sh:
#!/usr/bin/env bash CONTEXT="" # Load CLAUDE.md if exists if [ -f "$CLAUDE_PROJECT_DIR/CLAUDE.md" ]; then CONTEXT+="Project instructions from CLAUDE.md have been loaded.\n" fi # Add git status if [ -d "$CLAUDE_PROJECT_DIR/.git" ]; then BRANCH=$(git -C "$CLAUDE_PROJECT_DIR" branch --show-current 2>/dev/null) CONTEXT+="Current git branch: $BRANCH\n" fi # Output as JSON for structured context cat << EOF { "hookSpecificOutput": { "hookEventName": "SessionStart", "additionalContext": "$CONTEXT" } } EOF exit 0
4. Verify Before Stop (Stop)
Using prompt-based hook:
{ "hooks": { "Stop": [ { "hooks": [ { "type": "prompt", "prompt": "Evaluate if Claude should stop. Context: $ARGUMENTS\n\nCheck if:\n1. All requested tasks are complete\n2. No errors need addressing\n3. No tests need running\n\nRespond with: {\"decision\": \"approve\" or \"block\", \"reason\": \"explanation\"}" } ] } ] } }
5. Add Timestamp to Prompts (UserPromptSubmit)
hooks/hooks.json:
{ "hooks": { "UserPromptSubmit": [ { "hooks": [ { "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/scripts/add-timestamp.sh" } ] } ] } }
scripts/add-timestamp.sh:
#!/usr/bin/env bash # Plain text stdout is added as context echo "Current time: $(date '+%Y-%m-%d %H:%M:%S %Z')" exit 0
Environment Variables
Available in hooks:
- Absolute path to plugin directory${CLAUDE_PLUGIN_ROOT}
- Project root directory$CLAUDE_PROJECT_DIR
- (SessionStart only) File to persist env vars$CLAUDE_ENV_FILE
- "true" if running in web environment$CLAUDE_CODE_REMOTE
Persisting Environment (SessionStart)
#!/usr/bin/env bash if [ -n "$CLAUDE_ENV_FILE" ]; then echo 'export NODE_ENV=development' >> "$CLAUDE_ENV_FILE" echo 'export API_URL=http://localhost:3000' >> "$CLAUDE_ENV_FILE" fi exit 0
Best Practices
- Always quote variables: Use
not"$VAR"$VAR - Validate input: Never trust stdin blindly
- Use portable paths:
for plugin files${CLAUDE_PLUGIN_ROOT} - Set timeouts: Prevent hanging hooks
- Handle errors: Check for missing fields with
// empty - Keep hooks fast: Target <1 second execution
- Use jq for JSON: Safer than string parsing
Debugging
Enable debug mode:
claude --debug
Test hook manually:
echo '{"tool_name":"Write","tool_input":{"file_path":"test.txt"}}' | \ ./scripts/validate-write.sh echo $? # Check exit code
References
- Hooks documentation: https://code.claude.com/docs/en/hooks
- Hooks guide: https://code.claude.com/docs/en/hooks-guide
- Example hooks: https://github.com/anthropics/claude-code/tree/main/examples/hooks