| name | register-hook |
| description | Create and register hook scripts with proper error handling and settings |
| allowed-tools | Bash, Write, Read, Edit |
Register Hook Skill
Purpose: Create hook scripts with mandatory error handling patterns and register them in settings.json with proper matcher syntax.
Performance: Ensures hooks work correctly, prevents registration errors, enforces restart requirement
When to Use This Skill
✅ Use register-hook When:
- Creating new hook script from scratch
- Need to register hook in settings.json
- Want to ensure proper error handling pattern
- Setting up hook with specific trigger event
❌ Do NOT Use When:
- Modifying existing hook (use Edit tool)
- Hook already registered (verify first)
- Testing hook behavior (use manual execution)
What This Skill Does
1. Creates Hook Script
# Creates script with mandatory pattern:
#!/bin/bash
set -euo pipefail
# Error handler - output helpful message to stderr on failure
trap 'echo "ERROR in <script-name>.sh at line $LINENO: Command failed: $BASH_COMMAND" >&2; exit 1' ERR
# Hook logic...
2. Sets Permissions
chmod +x /workspace/main/.claude/hooks/{hook-name}.sh
3. Registers in settings.json
{
"hooks": {
"{TriggerEvent}": [
{
"matcher": "{tool-pattern}",
"hooks": [
{
"type": "command",
"command": "/workspace/.claude/hooks/{hook-name}.sh"
}
]
}
]
}
}
4. Warns About Restart
⚠️ Please restart Claude Code for hook changes to take effect
5. Provides Test Instructions
After restart, test hook with:
[specific command to trigger hook]
Trigger Events
Available Events
SessionStart: Runs when session starts or resumes after compaction
"SessionStart": [
{
"hooks": [{"type": "command", "command": "/workspace/.claude/hooks/my-hook.sh"}]
}
]
UserPromptSubmit: Runs when user submits a prompt
"UserPromptSubmit": [
{
"hooks": [{"type": "command", "command": "/workspace/.claude/hooks/my-hook.sh"}]
}
]
PreToolUse: Runs before tool execution (supports matchers)
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{"type": "command", "command": "/workspace/.claude/hooks/my-hook.sh"}]
}
]
PostToolUse: Runs after tool execution (supports matchers)
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [{"type": "command", "command": "/workspace/.claude/hooks/my-hook.sh"}]
}
]
PreCompact: Runs before context compaction
"PreCompact": [
{
"hooks": [{"type": "command", "command": "/workspace/.claude/hooks/my-hook.sh"}]
}
]
Matcher Syntax
Tool-Level Filtering
Matcher patterns filter by tool name only. Command/path filtering must be done inside hook scripts.
✅ CORRECT Matcher Syntax:
"matcher": "Bash" // Single tool
"matcher": "Write|Edit" // Multiple tools (regex)
"matcher": "Notebook.*" // Pattern matching
"matcher": "*" // All tools
// No matcher field = all tools
❌ WRONG Matcher Syntax:
"matcher": "tool:Bash && command:*git*" // ❌ 'tool:' prefix not supported
"matcher": "command:*echo*" // ❌ command filtering not in matcher
"matcher": "path:**/*.java" // ❌ path filtering not in matcher
Command/Path Filtering in Hook
# Inside hook script - parse JSON input
JSON_INPUT=$(cat)
COMMAND=$(echo "$JSON_INPUT" | jq -r '.tool_input.command // empty')
# Now filter based on command content
if [[ "$COMMAND" == *"git filter-branch"* ]]; then
echo "Blocking dangerous command" >&2
exit 2
fi
Usage
Create Simple Hook
# Create hook that logs all bash commands
HOOK_NAME="log-bash-commands"
TRIGGER="PreToolUse"
MATCHER="Bash"
/workspace/main/.claude/scripts/register-hook.sh \
--name "$HOOK_NAME" \
--trigger "$TRIGGER" \
--matcher "$MATCHER" \
--script-content "$(cat <<'SCRIPT'
#!/bin/bash
set -euo pipefail
trap 'echo "ERROR in log-bash-commands.sh at line $LINENO: Command failed: $BASH_COMMAND" >&2; exit 1' ERR
# Log bash command to file
JSON_INPUT=$(cat)
COMMAND=$(echo "$JSON_INPUT" | jq -r '.tool_input.command // "unknown"')
echo "[$(date -Iseconds)] $COMMAND" >> /tmp/bash-log.txt
SCRIPT
)"
Create Blocking Hook
# Create hook that blocks dangerous git commands
HOOK_NAME="block-dangerous-git"
TRIGGER="PreToolUse"
MATCHER="Bash"
/workspace/main/.claude/scripts/register-hook.sh \
--name "$HOOK_NAME" \
--trigger "$TRIGGER" \
--matcher "$MATCHER" \
--can-block true \
--script-content "$(cat <<'SCRIPT'
#!/bin/bash
set -euo pipefail
trap 'echo "ERROR in block-dangerous-git.sh at line $LINENO: Command failed: $BASH_COMMAND" >&2; exit 1' ERR
JSON_INPUT=$(cat)
COMMAND=$(echo "$JSON_INPUT" | jq -r '.tool_input.command // empty')
# Block dangerous commands
if [[ "$COMMAND" == *"git filter-branch"* ]] || [[ "$COMMAND" == *"--all"* ]]; then
echo '{"permissionDecision": "deny"}'
echo "❌ BLOCKED: Dangerous git command detected" >&2
exit 2
fi
SCRIPT
)"
Create Session Start Hook
# Create hook that runs on session start
HOOK_NAME="inject-session-context"
TRIGGER="SessionStart"
/workspace/main/.claude/scripts/register-hook.sh \
--name "$HOOK_NAME" \
--trigger "$TRIGGER" \
--script-content "$(cat <<'SCRIPT'
#!/bin/bash
set -euo pipefail
trap 'echo "ERROR in inject-session-context.sh at line $LINENO: Command failed: $BASH_COMMAND" >&2; exit 1' ERR
# Inject context at session start
echo "## Session Context"
echo "Working directory: $(pwd)"
echo "Git branch: $(git branch --show-current)"
echo "Active tasks: $(find /workspace/tasks -name task.json -exec jq -r 'select(.state != "COMPLETE") | .task_name' {} \;)"
SCRIPT
)"
Safety Features
Mandatory Error Handling
- ✅ Enforces
set -euo pipefail - ✅ Requires trap with ERR handler
- ✅ Stderr output for errors
- ✅ Helpful diagnostic information
Registration Validation
- ✅ Checks hook doesn't already exist
- ✅ Validates trigger event is valid
- ✅ Confirms matcher syntax correct
- ✅ Verifies settings.json is valid JSON
Permission Management
- ✅ Makes script executable
- ✅ Validates script is readable
- ✅ Confirms script at correct path
Workflow Integration
Complete Hook Creation Workflow
1. ✅ Identify hook need (what to trigger on)
2. ✅ Invoke register-hook skill
3. ✅ Skill creates script with error handling
4. ✅ Skill registers in settings.json
5. ✅ Skill warns about restart requirement
6. ✅ User restarts Claude Code
7. ✅ Test hook triggers correctly
8. ✅ Commit hook and settings.json together
Output Format
Script returns JSON:
{
"status": "success",
"message": "Hook registered successfully",
"hook_name": "log-bash-commands",
"hook_path": "/workspace/main/.claude/hooks/log-bash-commands.sh",
"trigger_event": "PreToolUse",
"matcher": "Bash",
"executable": true,
"registered": true,
"restart_required": true,
"test_command": "Bash tool with any command",
"timestamp": "2025-11-11T12:34:56-05:00"
}
Related
- CLAUDE.md § Hook Script Standards: Mandatory requirements
- CLAUDE.md § Hook Registration: Registration checklist
- settings.json: Hook configuration file
Troubleshooting
Hook Not Triggering After Registration
# Most common cause: Forgot to restart
# Fix: Restart Claude Code
# Verify hook registered:
cat /workspace/main/.claude/settings.json | jq '.hooks.PreToolUse'
# Verify hook executable:
test -x /workspace/main/.claude/hooks/my-hook.sh
echo $? # Should be 0
Hook Failing on Execution
# Check hook error output (stderr)
# Look for trap error message with line number
# Test hook manually:
echo '{"tool_name": "Bash", "tool_input": {"command": "echo test"}}' | \
/workspace/main/.claude/hooks/my-hook.sh
Matcher Not Working
# Remember: Matcher is tool-level only
# ✅ CORRECT: "matcher": "Bash"
# ❌ WRONG: "matcher": "Bash && command:git"
# For command filtering, parse JSON inside hook:
JSON_INPUT=$(cat)
COMMAND=$(echo "$JSON_INPUT" | jq -r '.tool_input.command')
Settings.json Invalid After Registration
# Validate JSON syntax:
jq empty /workspace/main/.claude/settings.json
# If invalid, fix manually:
# 1. Open settings.json
# 2. Find syntax error (trailing comma, missing bracket, etc.)
# 3. Fix error
# 4. Validate: jq empty settings.json
# 5. Restart Claude Code
Common Patterns
Pattern 1: Validation Hook
# Validate something before tool execution
PreToolUse + can-block = validation hook
Pattern 2: Logging Hook
# Log tool usage for audit
PostToolUse + no blocking = logging hook
Pattern 3: Context Injection Hook
# Add context at session start
SessionStart + echo statements = context injection
Pattern 4: User Prompt Hook
# React to user input
UserPromptSubmit + parse prompt = prompt hook
Implementation Notes
The register-hook script performs:
Validation Phase
- Check hook name not already used
- Validate trigger event valid
- Confirm matcher syntax correct
- Verify settings.json accessible
Script Creation Phase
- Write script with mandatory error handling
- Add provided hook logic
- Make executable (chmod +x)
- Verify script readable
Registration Phase
- Parse settings.json
- Add hook to appropriate trigger event
- Add matcher if specified
- Write updated settings.json
Verification Phase
- Validate settings.json still valid JSON
- Confirm hook entry exists
- Check script executable
- Warn about restart requirement
Documentation Phase
- Return success status
- Provide test instructions
- List next steps