Claude-skill-registry hooks-configuration
git clone https://github.com/majiayu000/claude-skill-registry
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-configuration" ~/.claude/skills/majiayu000-claude-skill-registry-hooks-configuration && rm -rf "$T"
skills/data/hooks-configuration/SKILL.mdClaude Code Hooks Configuration
Expert knowledge for configuring and developing Claude Code hooks to automate workflows and enforce best practices.
Core Concepts
What Are Hooks? Hooks are user-defined shell commands that execute at specific points in Claude Code's lifecycle. Unlike relying on Claude to "decide" to run something, hooks provide deterministic, guaranteed execution.
Why Use Hooks?
- Enforce code formatting automatically
- Block dangerous commands before execution
- Inject context at session start
- Log commands for audit trails
- Send notifications when tasks complete
Hook Lifecycle Events
| Event | When It Fires | Key Use Cases |
|---|---|---|
| SessionStart | Session begins/resumes | Environment setup, context loading |
| UserPromptSubmit | User submits prompt | Input validation, context injection |
| PreToolUse | Before tool execution | Permission control, blocking dangerous ops |
| PostToolUse | After tool completes | Auto-formatting, logging, validation |
| Stop | Agent finishes | Notifications, git reminders |
| SubagentStart | Subagent is about to start | Input modification, context injection |
| SubagentStop | Subagent finishes | Task completion evaluation |
| PreCompact | Before context compaction | Transcript backup |
| Notification | Claude sends notification | Custom alerts |
| SessionEnd | Session terminates | Cleanup, state persistence |
Configuration
File Locations
Hooks are configured in settings files:
- User-level (applies everywhere)~/.claude/settings.json
- Project-level (committed to repo).claude/settings.json
- Local project (not committed).claude/settings.local.json
Claude Code merges all matching hooks from all files.
Frontmatter Hooks (Skills and Commands)
Hooks can also be defined directly in skill and command frontmatter using the
hooks field:
--- name: my-skill description: A skill with hooks allowed-tools: Bash, Read hooks: PreToolUse: - matcher: "Bash" hooks: - type: command command: "echo 'Pre-tool hook from skill'" timeout: 10 ---
This allows skills and commands to define their own hooks that are active only when that skill/command is in use.
Basic Structure
{ "hooks": { "EventName": [ { "matcher": "ToolPattern", "hooks": [ { "type": "command", "command": "your-command-here", "timeout": 30 } ] } ] } }
Matcher Patterns
- Exact match:
- matches exactly "Bash" tool"Bash" - Regex patterns:
- matches either tool"Edit|Write" - Wildcards:
- matches tools starting with "Notebook""Notebook.*" - All tools:
- matches everything"*" - MCP tools:
- targets MCP server tools"mcp__server__tool"
Input Schema
Hooks receive JSON via stdin with these common fields:
{ "session_id": "unique-session-id", "transcript_path": "/path/to/conversation.json", "cwd": "/current/working/directory", "permission_mode": "mode", "hook_event_name": "PreToolUse" }
PreToolUse additional fields:
{ "tool_name": "Bash", "tool_input": { "command": "npm test" } }
PostToolUse additional fields:
{ "tool_name": "Bash", "tool_input": { ... }, "tool_response": { ... } }
SubagentStart additional fields:
{ "subagent_type": "Explore", "subagent_prompt": "original prompt text", "subagent_model": "claude-sonnet-4-20250514" }
Output Schema
Exit Codes
- 0: Success (command allowed)
- 2: Blocking error (stderr shown to Claude, operation blocked)
- Other: Non-blocking error (logged in verbose mode)
JSON Response (optional)
PreToolUse:
{ "permissionDecision": "allow|deny|ask", "permissionDecisionReason": "explanation", "updatedInput": { "modified": "input" } }
Stop/SubagentStop:
{ "decision": "block", "reason": "required explanation for continuing" }
SubagentStart (input modification):
{ "updatedPrompt": "modified prompt text to inject context or modify behavior" }
SessionStart:
{ "additionalContext": "Information to inject into session" }
Common Hook Patterns
Block Dangerous Commands (PreToolUse)
#!/bin/bash INPUT=$(cat) COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty') # Block rm -rf / if echo "$COMMAND" | grep -Eq 'rm\s+(-rf|-fr)\s+/'; then echo "BLOCKED: Refusing to run destructive command on root" >&2 exit 2 fi exit 0
Auto-Format After Edits (PostToolUse)
#!/bin/bash INPUT=$(cat) FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty') if [[ "$FILE" == *.py ]]; then ruff format "$FILE" 2>/dev/null ruff check --fix "$FILE" 2>/dev/null elif [[ "$FILE" == *.ts ]] || [[ "$FILE" == *.tsx ]]; then prettier --write "$FILE" 2>/dev/null fi exit 0
Remind About Built-in Tools (PreToolUse)
#!/bin/bash INPUT=$(cat) COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty') if echo "$COMMAND" | grep -Eq '^\s*cat\s+[^|><]'; then echo "REMINDER: Use the Read tool instead of 'cat'" >&2 exit 2 fi exit 0
Load Context at Session Start (SessionStart)
#!/bin/bash GIT_STATUS=$(git status --short 2>/dev/null | head -5) BRANCH=$(git branch --show-current 2>/dev/null) cat << EOF { "additionalContext": "Current branch: $BRANCH\nPending changes:\n$GIT_STATUS" } EOF
Inject Context for Subagents (SubagentStart)
#!/bin/bash INPUT=$(cat) SUBAGENT_TYPE=$(echo "$INPUT" | jq -r '.subagent_type // empty') ORIGINAL_PROMPT=$(echo "$INPUT" | jq -r '.subagent_prompt // empty') # Add project context to Explore agents if [ "$SUBAGENT_TYPE" = "Explore" ]; then PROJECT_INFO="Project uses TypeScript with Bun. Main source in src/." cat << EOF { "updatedPrompt": "$PROJECT_INFO\n\n$ORIGINAL_PROMPT" } EOF fi exit 0
Desktop Notification on Stop (Stop)
#!/bin/bash # Linux notify-send "Claude Code" "Task completed" 2>/dev/null # macOS osascript -e 'display notification "Task completed" with title "Claude Code"' 2>/dev/null exit 0
Audit Logging (PostToolUse)
#!/bin/bash INPUT=$(cat) TOOL=$(echo "$INPUT" | jq -r '.tool_name') COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // "N/A"') echo "$(date -Iseconds) | $TOOL | $COMMAND" >> ~/.claude/audit.log exit 0
Configuration Examples
Anti-Pattern Detection
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "bash $CLAUDE_PROJECT_DIR/hooks-plugin/hooks/bash-antipatterns.sh", "timeout": 5 } ] } ] } }
Auto-Format Python Files
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "bash -c 'FILE=$(cat | jq -r \".tool_input.file_path\"); [[ \"$FILE\" == *.py ]] && ruff format \"$FILE\"'" } ] } ] } }
Git Reminder on Stop
{ "hooks": { "Stop": [ { "matcher": "*", "hooks": [ { "type": "command", "command": "bash -c 'changes=$(git status --porcelain | wc -l); [ $changes -gt 0 ] && echo \"Reminder: $changes uncommitted changes\"'" } ] } ] } }
Best Practices
Script Development:
- Always read input from stdin with
cat - Use
for JSON parsingjq - Quote all variables to prevent injection
- Exit with code 2 to block, 0 to allow
- Write blocking messages to stderr
- Keep hooks fast (< 5 seconds)
Configuration:
- Use
for portable paths$CLAUDE_PROJECT_DIR - Set appropriate timeouts (default: 60s)
- Use specific matchers over wildcards
- Test hooks manually before enabling
Security:
- Validate all inputs
- Use absolute paths
- Avoid touching
or.env
directly.git/ - Review hook code before deployment
Debugging
Verify hook registration:
/hooks
Enable debug logging:
claude --debug
Test hooks manually:
echo '{"tool_input": {"command": "cat file.txt"}}' | bash your-hook.sh echo $? # Check exit code
Available Hooks in This Plugin
- bash-antipatterns.sh: Detects when Claude uses shell commands instead of built-in tools (cat, grep, sed, timeout, etc.)
See
hooks/README.md for full documentation.