docs: Rewrite hooks documentation based on official Claude Code docs
- Update configuration format to use array-based matcher/hooks structure - Document all 9 official hook events (PreToolUse, PermissionRequest, PostToolUse, Notification, UserPromptSubmit, Stop, SubagentStop, PreCompact, SessionStart, SessionEnd) - Add JSON stdin input/output documentation with schemas - Document exit code semantics (0=success, 2=blocking, other=warning) - Add prompt-based hooks for Stop/SubagentStop events - Add environment variables (CLAUDE_PROJECT_DIR, CLAUDE_ENV_FILE, etc.) - Add security considerations with official disclaimer - Add MCP tool hook patterns (mcp__<server>__<tool>) - Update example scripts to use JSON stdin parsing - Add debugging section with claude --debug flag - Remove incorrect hook types (PreCommit, PostCommit, PrePush) Based on official documentation: https://code.claude.com/docs/en/hooks
This commit is contained in:
@@ -2,330 +2,650 @@
|
||||
|
||||
# Hooks
|
||||
|
||||
Hooks are event-driven shell commands that execute automatically in response to Claude Code events. They enable automation, validation, and custom workflows without manual intervention.
|
||||
Hooks are automated scripts that execute in response to specific events during Claude Code sessions. They enable automation, validation, permission management, and custom workflows.
|
||||
|
||||
## Overview
|
||||
|
||||
Hooks are shell commands that execute automatically in response to specific events in Claude Code. They enable custom workflows, validation, and automation. Hooks are triggered by:
|
||||
- **Pre-hooks**: Run before an action
|
||||
- **Post-hooks**: Run after an action
|
||||
- **Validation hooks**: Check conditions before proceeding
|
||||
Hooks are shell commands or LLM prompts that execute automatically when specific events occur in Claude Code. They receive JSON input via stdin and communicate results via exit codes and JSON stdout output.
|
||||
|
||||
Hooks enable you to automate repetitive tasks, enforce code quality standards, and create seamless development workflows.
|
||||
|
||||
## Hook Types
|
||||
|
||||
| Hook Type | Event | Use Cases |
|
||||
|-----------|-------|-----------|
|
||||
| **Tool Hooks** | PreToolUse:*, PostToolUse:* | Auto-formatting, validation, logging |
|
||||
| **Session Hooks** | UserPromptSubmit, SessionStart, SessionEnd | Input validation, initialization, cleanup |
|
||||
| **Git Hooks** | PreCommit, PostCommit, PrePush | Testing, linting, notifications |
|
||||
|
||||
## Available Hook Types (Detailed)
|
||||
|
||||
### 1. Tool Hooks
|
||||
Execute before/after tool usage:
|
||||
- `PreToolUse:{ToolName}` - Before a tool is used
|
||||
- `PostToolUse:{ToolName}` - After a tool completes
|
||||
- Examples: `PreToolUse:Edit`, `PostToolUse:Bash`, `PreToolUse:Write`
|
||||
|
||||
### 2. Session Hooks
|
||||
Execute during session lifecycle:
|
||||
- `UserPromptSubmit` - Before processing user input
|
||||
- `SessionStart` - When a new session begins
|
||||
- `SessionEnd` - When a session ends
|
||||
|
||||
### 3. Git Hooks
|
||||
Execute during git operations:
|
||||
- `PreCommit` - Before creating a commit
|
||||
- `PostCommit` - After commit completes
|
||||
- `PrePush` - Before pushing to remote
|
||||
|
||||
## Common Hooks
|
||||
|
||||
```bash
|
||||
# Pre-commit hook - run tests
|
||||
PreCommit: "npm test"
|
||||
|
||||
# Post-write hook - format code
|
||||
PostToolUse:Write: "prettier --write ${file_path}"
|
||||
|
||||
# Pre-tool-use hook - validate
|
||||
PreToolUse:Edit: "eslint ${file_path}"
|
||||
|
||||
# User prompt validation
|
||||
UserPromptSubmit: "~/.claude/hooks/validate-prompt.sh"
|
||||
```
|
||||
|
||||
## Hook Variables
|
||||
|
||||
Available variables in hook commands:
|
||||
- `${file_path}` - Path to file being edited/written
|
||||
- `${command}` - Command being executed (Bash hooks)
|
||||
- `${tool_name}` - Name of the tool being used
|
||||
- `${session_id}` - Current session identifier
|
||||
**Key features:**
|
||||
- Event-driven automation
|
||||
- JSON-based input/output
|
||||
- Support for command and prompt-based hooks
|
||||
- Pattern matching for tool-specific hooks
|
||||
|
||||
## Configuration
|
||||
|
||||
Hooks are configured in Claude Code settings:
|
||||
Hooks are configured in settings files with a specific structure:
|
||||
|
||||
- `~/.claude/settings.json` - User settings (global)
|
||||
- `.claude/settings.json` - Project settings (committed)
|
||||
- `.claude/settings.local.json` - Local project settings (not committed)
|
||||
|
||||
### Basic Configuration Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse:Edit": "eslint --fix ${file_path}",
|
||||
"PostToolUse:Bash": "echo 'Command completed' >> /tmp/bash.log",
|
||||
"UserPromptSubmit": "~/.claude/hooks/validate-prompt.sh",
|
||||
"PreCommit": "npm test"
|
||||
"EventName": [
|
||||
{
|
||||
"matcher": "ToolPattern",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "your-command-here",
|
||||
"timeout": 60
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key fields:**
|
||||
|
||||
| Field | Description | Example |
|
||||
|-------|-------------|---------|
|
||||
| `matcher` | Pattern to match tool names (case-sensitive) | `"Write"`, `"Edit\|Write"`, `"*"` |
|
||||
| `hooks` | Array of hook definitions | `[{ "type": "command", ... }]` |
|
||||
| `type` | Hook type: `"command"` (bash) or `"prompt"` (LLM) | `"command"` |
|
||||
| `command` | Shell command to execute | `"$CLAUDE_PROJECT_DIR/.claude/hooks/format.sh"` |
|
||||
| `timeout` | Optional timeout in seconds (default 60) | `30` |
|
||||
|
||||
### Matcher Patterns
|
||||
|
||||
| Pattern | Description | Example |
|
||||
|---------|-------------|---------|
|
||||
| Exact string | Matches specific tool | `"Write"` |
|
||||
| Regex pattern | Matches multiple tools | `"Edit\|Write"` |
|
||||
| Wildcard | Matches all tools | `"*"` or `""` |
|
||||
| MCP tools | Server and tool pattern | `"mcp__memory__.*"` |
|
||||
|
||||
## Hook Events
|
||||
|
||||
Claude Code supports **9 hook events**:
|
||||
|
||||
| Event | When Triggered | Supports Matchers | Can Block | Common Use |
|
||||
|-------|---------------|-------------------|-----------|------------|
|
||||
| **PreToolUse** | Before tool execution | Yes (tool names) | Yes | Validate, modify inputs |
|
||||
| **PermissionRequest** | Permission dialog shown | Yes (tool names) | Yes | Auto-approve/deny |
|
||||
| **PostToolUse** | After tool completion | Yes (tool names) | Yes (block) | Add context, feedback |
|
||||
| **Notification** | Notification sent | Yes (notification types) | No | Custom notifications |
|
||||
| **UserPromptSubmit** | Before prompt processed | No | Yes | Validate prompts |
|
||||
| **Stop** | Agent finishes responding | No | Yes | Task completion check |
|
||||
| **SubagentStop** | Subagent finishes | No | Yes | Subagent validation |
|
||||
| **PreCompact** | Before compact operation | Yes (manual/auto) | No | Pre-compact actions |
|
||||
| **SessionStart** | Session begins/resumes | Yes (startup/resume/clear/compact) | No | Environment setup |
|
||||
| **SessionEnd** | Session ends | No | No | Cleanup |
|
||||
|
||||
### PreToolUse
|
||||
|
||||
Runs after Claude creates tool parameters and before processing. Use this to validate or modify tool inputs.
|
||||
|
||||
**Configuration:**
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.py"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Common matchers:** `Task`, `Bash`, `Glob`, `Grep`, `Read`, `Edit`, `Write`, `WebFetch`, `WebSearch`
|
||||
|
||||
**Output control:**
|
||||
- `permissionDecision`: `"allow"`, `"deny"`, or `"ask"`
|
||||
- `permissionDecisionReason`: Explanation for decision
|
||||
- `updatedInput`: Modified tool input parameters
|
||||
|
||||
### PostToolUse
|
||||
|
||||
Runs immediately after tool completion. Use for verification, logging, or providing context back to Claude.
|
||||
|
||||
**Configuration:**
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/security-scan.py"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Output control:**
|
||||
- `"block"` decision prompts Claude with feedback
|
||||
- `additionalContext`: Context added for Claude
|
||||
|
||||
### UserPromptSubmit
|
||||
|
||||
Runs when user submits a prompt, before Claude processes it.
|
||||
|
||||
**Configuration:**
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-prompt.py"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Output control:**
|
||||
- `decision`: `"block"` to prevent processing
|
||||
- `reason`: Explanation if blocked
|
||||
- `additionalContext`: Context added to prompt
|
||||
|
||||
### Stop and SubagentStop
|
||||
|
||||
Run when Claude finishes responding (Stop) or a subagent completes (SubagentStop). Supports prompt-based evaluation for intelligent task completion checking.
|
||||
|
||||
**Configuration:**
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"Stop": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "prompt",
|
||||
"prompt": "Evaluate if Claude completed all requested tasks.",
|
||||
"timeout": 30
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### SessionStart
|
||||
|
||||
Runs when session starts or resumes. Can persist environment variables.
|
||||
|
||||
**Matchers:** `startup`, `resume`, `clear`, `compact`
|
||||
|
||||
**Special feature:** Use `CLAUDE_ENV_FILE` to persist environment variables:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
if [ -n "$CLAUDE_ENV_FILE" ]; then
|
||||
echo 'export NODE_ENV=development' >> "$CLAUDE_ENV_FILE"
|
||||
fi
|
||||
exit 0
|
||||
```
|
||||
|
||||
## Hook Input and Output
|
||||
|
||||
### JSON Input (via stdin)
|
||||
|
||||
All hooks receive JSON input via stdin:
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "abc123",
|
||||
"transcript_path": "/path/to/transcript.jsonl",
|
||||
"cwd": "/current/working/directory",
|
||||
"permission_mode": "default",
|
||||
"hook_event_name": "PreToolUse",
|
||||
"tool_name": "Write",
|
||||
"tool_input": {
|
||||
"file_path": "/path/to/file.js",
|
||||
"content": "..."
|
||||
},
|
||||
"tool_use_id": "toolu_01ABC123..."
|
||||
}
|
||||
```
|
||||
|
||||
### Exit Codes
|
||||
|
||||
| Exit Code | Meaning | Behavior |
|
||||
|-----------|---------|----------|
|
||||
| **0** | Success | Continue, parse JSON stdout |
|
||||
| **2** | Blocking error | Block operation, stderr shown as error |
|
||||
| **Other** | Non-blocking error | Continue, stderr shown in verbose mode |
|
||||
|
||||
### JSON Output (stdout, exit code 0)
|
||||
|
||||
```json
|
||||
{
|
||||
"continue": true,
|
||||
"stopReason": "Optional message if stopping",
|
||||
"suppressOutput": false,
|
||||
"systemMessage": "Optional warning message",
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "allow",
|
||||
"permissionDecisionReason": "File is in allowed directory",
|
||||
"updatedInput": {
|
||||
"file_path": "/modified/path.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Availability | Description |
|
||||
|----------|-------------|-------------|
|
||||
| `CLAUDE_PROJECT_DIR` | All hooks | Absolute path to project root |
|
||||
| `CLAUDE_ENV_FILE` | SessionStart only | File path for persisting env vars |
|
||||
| `CLAUDE_CODE_REMOTE` | All hooks | `"true"` if running in web environment |
|
||||
| `${CLAUDE_PLUGIN_ROOT}` | Plugin hooks | Path to plugin directory |
|
||||
|
||||
## Prompt-Based Hooks
|
||||
|
||||
For `Stop` and `SubagentStop` events, you can use LLM-based evaluation:
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"Stop": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "prompt",
|
||||
"prompt": "Review if all tasks are complete. Return your decision.",
|
||||
"timeout": 30
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**LLM Response Schema:**
|
||||
```json
|
||||
{
|
||||
"decision": "approve",
|
||||
"reason": "All tasks completed successfully",
|
||||
"continue": false,
|
||||
"stopReason": "Task complete"
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Example 1: Auto-format Code on Edit
|
||||
### Example 1: Bash Command Validator (PreToolUse)
|
||||
|
||||
**Hook**: `PreToolUse:Write`
|
||||
**File:** `.claude/hooks/validate-bash.py`
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# ~/.claude/hooks/format-code.sh
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import sys
|
||||
import re
|
||||
|
||||
FILE=$1
|
||||
BLOCKED_PATTERNS = [
|
||||
(r"\brm\s+-rf\s+/", "Blocking dangerous rm -rf / command"),
|
||||
(r"\bsudo\s+rm", "Blocking sudo rm command"),
|
||||
]
|
||||
|
||||
# Detect file type and format accordingly
|
||||
case "$FILE" in
|
||||
*.js|*.jsx|*.ts|*.tsx)
|
||||
prettier --write "$FILE"
|
||||
;;
|
||||
*.py)
|
||||
black "$FILE"
|
||||
;;
|
||||
*.go)
|
||||
gofmt -w "$FILE"
|
||||
;;
|
||||
esac
|
||||
def main():
|
||||
input_data = json.load(sys.stdin)
|
||||
|
||||
tool_name = input_data.get("tool_name", "")
|
||||
if tool_name != "Bash":
|
||||
sys.exit(0)
|
||||
|
||||
command = input_data.get("tool_input", {}).get("command", "")
|
||||
|
||||
for pattern, message in BLOCKED_PATTERNS:
|
||||
if re.search(pattern, command):
|
||||
print(message, file=sys.stderr)
|
||||
sys.exit(2) # Exit 2 = blocking error
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
**Configuration**:
|
||||
**Configuration:**
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse:Write": "~/.claude/hooks/format-code.sh ${file_path}"
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.py\""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 2: Run Tests Before Commit
|
||||
### Example 2: Security Scanner (PostToolUse)
|
||||
|
||||
**Hook**: `PreCommit`
|
||||
**File:** `.claude/hooks/security-scan.py`
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import sys
|
||||
import re
|
||||
|
||||
SECRET_PATTERNS = [
|
||||
(r"password\s*=\s*['\"][^'\"]+['\"]", "Potential hardcoded password"),
|
||||
(r"api[_-]?key\s*=\s*['\"][^'\"]+['\"]", "Potential hardcoded API key"),
|
||||
]
|
||||
|
||||
def main():
|
||||
input_data = json.load(sys.stdin)
|
||||
|
||||
tool_name = input_data.get("tool_name", "")
|
||||
if tool_name not in ["Write", "Edit"]:
|
||||
sys.exit(0)
|
||||
|
||||
tool_input = input_data.get("tool_input", {})
|
||||
content = tool_input.get("content", "") or tool_input.get("new_string", "")
|
||||
file_path = tool_input.get("file_path", "")
|
||||
|
||||
warnings = []
|
||||
for pattern, message in SECRET_PATTERNS:
|
||||
if re.search(pattern, content, re.IGNORECASE):
|
||||
warnings.append(message)
|
||||
|
||||
if warnings:
|
||||
output = {
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PostToolUse",
|
||||
"additionalContext": f"Security warnings for {file_path}: " + "; ".join(warnings)
|
||||
}
|
||||
}
|
||||
print(json.dumps(output))
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
### Example 3: Auto-Format Code (PostToolUse)
|
||||
|
||||
**File:** `.claude/hooks/format-code.sh`
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# ~/.claude/hooks/pre-commit.sh
|
||||
|
||||
echo "Running tests before commit..."
|
||||
# Read JSON from stdin
|
||||
INPUT=$(cat)
|
||||
TOOL_NAME=$(echo "$INPUT" | python3 -c "import sys, json; print(json.load(sys.stdin).get('tool_name', ''))")
|
||||
FILE_PATH=$(echo "$INPUT" | python3 -c "import sys, json; print(json.load(sys.stdin).get('tool_input', {}).get('file_path', ''))")
|
||||
|
||||
# Run tests
|
||||
npm test
|
||||
if [ "$TOOL_NAME" != "Write" ] && [ "$TOOL_NAME" != "Edit" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Format based on file extension
|
||||
case "$FILE_PATH" in
|
||||
*.js|*.jsx|*.ts|*.tsx|*.json)
|
||||
command -v prettier &>/dev/null && prettier --write "$FILE_PATH" 2>/dev/null
|
||||
;;
|
||||
*.py)
|
||||
command -v black &>/dev/null && black "$FILE_PATH" 2>/dev/null
|
||||
;;
|
||||
*.go)
|
||||
command -v gofmt &>/dev/null && gofmt -w "$FILE_PATH" 2>/dev/null
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
```
|
||||
|
||||
### Example 4: Prompt Validator (UserPromptSubmit)
|
||||
|
||||
**File:** `.claude/hooks/validate-prompt.py`
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import sys
|
||||
import re
|
||||
|
||||
BLOCKED_PATTERNS = [
|
||||
(r"delete\s+(all\s+)?database", "Dangerous: database deletion"),
|
||||
(r"rm\s+-rf\s+/", "Dangerous: root deletion"),
|
||||
]
|
||||
|
||||
def main():
|
||||
input_data = json.load(sys.stdin)
|
||||
prompt = input_data.get("user_prompt", "") or input_data.get("prompt", "")
|
||||
|
||||
for pattern, message in BLOCKED_PATTERNS:
|
||||
if re.search(pattern, prompt, re.IGNORECASE):
|
||||
output = {
|
||||
"decision": "block",
|
||||
"reason": f"Blocked: {message}"
|
||||
}
|
||||
print(json.dumps(output))
|
||||
sys.exit(0)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
### Example 5: Intelligent Stop Hook (Prompt-Based)
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"Stop": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "prompt",
|
||||
"prompt": "Review if Claude completed all requested tasks. Check: 1) Were all files created/modified? 2) Were there unresolved errors? If incomplete, explain what's missing.",
|
||||
"timeout": 30
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## MCP Tool Hooks
|
||||
|
||||
MCP tools follow the pattern `mcp__<server>__<tool>`:
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "mcp__memory__.*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "echo '{\"systemMessage\": \"Memory operation logged\"}'"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Disclaimer
|
||||
|
||||
**USE AT YOUR OWN RISK**: Hooks execute arbitrary shell commands. You are solely responsible for:
|
||||
- Commands you configure
|
||||
- File access/modification permissions
|
||||
- Potential data loss or system damage
|
||||
- Testing hooks in safe environments before production use
|
||||
|
||||
### Best Practices
|
||||
|
||||
| Do | Don't |
|
||||
|-----|-------|
|
||||
| Validate and sanitize all inputs | Trust input data blindly |
|
||||
| Quote shell variables: `"$VAR"` | Use unquoted: `$VAR` |
|
||||
| Block path traversal (`..`) | Allow arbitrary paths |
|
||||
| Use absolute paths with `$CLAUDE_PROJECT_DIR` | Hardcode paths |
|
||||
| Skip sensitive files (`.env`, `.git/`, keys) | Process all files |
|
||||
| Test hooks in isolation first | Deploy untested hooks |
|
||||
|
||||
## Debugging
|
||||
|
||||
### Enable Debug Mode
|
||||
|
||||
Run Claude with debug flag for detailed hook logs:
|
||||
|
||||
```bash
|
||||
claude --debug
|
||||
```
|
||||
|
||||
### Verbose Mode
|
||||
|
||||
Use `Ctrl+O` in Claude Code to enable verbose mode and see hook execution progress.
|
||||
|
||||
### Test Hooks Independently
|
||||
|
||||
```bash
|
||||
# Test with sample JSON input
|
||||
echo '{"tool_name": "Bash", "tool_input": {"command": "ls -la"}}' | python3 .claude/hooks/validate-bash.py
|
||||
|
||||
# Check exit code
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "❌ Tests failed! Commit blocked."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Tests passed! Proceeding with commit."
|
||||
exit 0
|
||||
echo $?
|
||||
```
|
||||
|
||||
**Configuration**:
|
||||
## Complete Configuration Example
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PreCommit": "~/.claude/hooks/pre-commit.sh"
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.py\"",
|
||||
"timeout": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/format-code.sh\"",
|
||||
"timeout": 30
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/security-scan.py\"",
|
||||
"timeout": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate-prompt.py\""
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"SessionStart": [
|
||||
{
|
||||
"matcher": "startup",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/session-init.sh\""
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Stop": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "prompt",
|
||||
"prompt": "Verify all tasks are complete before stopping.",
|
||||
"timeout": 30
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 3: Security Scan on File Write
|
||||
## Hook Execution Details
|
||||
|
||||
**Hook**: `PostToolUse:Write`
|
||||
| Aspect | Behavior |
|
||||
|--------|----------|
|
||||
| **Timeout** | 60 seconds default, configurable per command |
|
||||
| **Parallelization** | All matching hooks run in parallel |
|
||||
| **Deduplication** | Identical hook commands deduplicated |
|
||||
| **Environment** | Runs in current directory with Claude Code's environment |
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# ~/.claude/hooks/security-scan.sh
|
||||
## Troubleshooting
|
||||
|
||||
FILE=$1
|
||||
### Hook Not Executing
|
||||
- Verify JSON configuration syntax is correct
|
||||
- Check matcher pattern matches the tool name
|
||||
- Ensure script exists and is executable: `chmod +x script.sh`
|
||||
- Run `claude --debug` to see hook execution logs
|
||||
- Verify hook reads JSON from stdin (not command args)
|
||||
|
||||
# Check for common security issues
|
||||
if grep -q "password\s*=\s*['\"]" "$FILE"; then
|
||||
echo "⚠️ WARNING: Potential hardcoded password detected in $FILE"
|
||||
fi
|
||||
### Hook Blocks Unexpectedly
|
||||
- Test hook with sample JSON: `echo '{"tool_name": "Write", ...}' | ./hook.py`
|
||||
- Check exit code: should be 0 for allow, 2 for block
|
||||
- Check stderr output (shown on exit code 2)
|
||||
|
||||
if grep -q "api[_-]key\s*=\s*['\"]" "$FILE"; then
|
||||
echo "⚠️ WARNING: Potential hardcoded API key detected in $FILE"
|
||||
fi
|
||||
### JSON Parsing Errors
|
||||
- Always read from stdin, not command arguments
|
||||
- Use proper JSON parsing (not string manipulation)
|
||||
- Handle missing fields gracefully
|
||||
|
||||
# Scan with semgrep if available
|
||||
if command -v semgrep &> /dev/null; then
|
||||
semgrep --config=auto "$FILE" --quiet
|
||||
fi
|
||||
```
|
||||
|
||||
**Configuration**:
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PostToolUse:Write": "~/.claude/hooks/security-scan.sh ${file_path}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 4: Log All Bash Commands
|
||||
|
||||
**Hook**: `PostToolUse:Bash`
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# ~/.claude/hooks/log-bash.sh
|
||||
|
||||
COMMAND=$1
|
||||
TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")
|
||||
LOGFILE="$HOME/.claude/bash-commands.log"
|
||||
|
||||
echo "[$TIMESTAMP] $COMMAND" >> "$LOGFILE"
|
||||
```
|
||||
|
||||
**Configuration**:
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PostToolUse:Bash": "~/.claude/hooks/log-bash.sh '${command}'"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 5: Validate User Prompts
|
||||
|
||||
**Hook**: `UserPromptSubmit`
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# ~/.claude/hooks/validate-prompt.sh
|
||||
|
||||
# Read prompt from stdin
|
||||
PROMPT=$(cat)
|
||||
|
||||
# Check for prohibited patterns
|
||||
if echo "$PROMPT" | grep -qi "delete database"; then
|
||||
echo "❌ Blocked: Dangerous operation detected"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for required context
|
||||
if echo "$PROMPT" | grep -qi "deploy to production"; then
|
||||
if [ ! -f ".deployment-approved" ]; then
|
||||
echo "❌ Blocked: Production deployment requires approval file"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
```
|
||||
|
||||
**Configuration**:
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"UserPromptSubmit": "~/.claude/hooks/validate-prompt.sh"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Hook Exit Codes
|
||||
|
||||
Hooks communicate results via exit codes:
|
||||
- `0` - Success, continue operation
|
||||
- `1` - Failure, block operation (for pre-hooks)
|
||||
- Other codes - Treated as warnings
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Do's ✅
|
||||
- Keep hooks fast (< 1 second)
|
||||
- Use hooks for validation and automation
|
||||
- Handle errors gracefully
|
||||
- Use absolute paths
|
||||
- Log important events
|
||||
- Make hooks idempotent
|
||||
- Test hooks independently before deploying
|
||||
|
||||
### Don'ts ❌
|
||||
- Make hooks interactive
|
||||
- Use hooks for long-running tasks
|
||||
- Hardcode credentials
|
||||
- Ignore hook failures silently
|
||||
- Modify files unexpectedly
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### Code Quality
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse:Write": "eslint --fix ${file_path}",
|
||||
"PostToolUse:Write": "prettier --check ${file_path}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Security
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PostToolUse:Write": "~/.claude/hooks/security-scan.sh ${file_path}",
|
||||
"PreCommit": "git secrets --scan"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Testing
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PreCommit": "npm test",
|
||||
"PostToolUse:Edit": "jest --findRelatedTests ${file_path}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Deployment
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PrePush": "npm run build && npm run test:e2e",
|
||||
"PostPush": "~/.claude/hooks/notify-team.sh"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Debugging Hooks
|
||||
|
||||
Enable hook debugging:
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"debug": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
View hook execution logs:
|
||||
```bash
|
||||
tail -f ~/.claude/hooks.log
|
||||
```
|
||||
|
||||
## Installation Instructions
|
||||
## Installation
|
||||
|
||||
### Step 1: Create Hooks Directory
|
||||
```bash
|
||||
@@ -334,71 +654,20 @@ mkdir -p ~/.claude/hooks
|
||||
|
||||
### Step 2: Copy Example Hooks
|
||||
```bash
|
||||
cp 07-hooks/*.sh ~/.claude/hooks/
|
||||
cp 06-hooks/*.sh ~/.claude/hooks/
|
||||
chmod +x ~/.claude/hooks/*.sh
|
||||
```
|
||||
|
||||
### Step 3: Configure in Settings
|
||||
Edit `~/.claude/settings.json` to add hook configurations:
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse:Write": "~/.claude/hooks/format-code.sh ${file_path}",
|
||||
"PostToolUse:Write": "~/.claude/hooks/security-scan.sh ${file_path}",
|
||||
"PreCommit": "~/.claude/hooks/pre-commit.sh",
|
||||
"UserPromptSubmit": "~/.claude/hooks/validate-prompt.sh"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Make Scripts Executable
|
||||
```bash
|
||||
chmod +x ~/.claude/hooks/*.sh
|
||||
```
|
||||
|
||||
### Step 5: Test Your Hooks
|
||||
```bash
|
||||
# Enable debug mode for testing
|
||||
# Set "debug": true in hooks configuration
|
||||
tail -f ~/.claude/hooks.log
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Hook Not Executing
|
||||
1. Check hook path is correct
|
||||
2. Verify file has execute permissions
|
||||
3. Check hook name matches event exactly
|
||||
4. Enable debug mode
|
||||
|
||||
### Hook Blocking Operations
|
||||
1. Check hook exit code
|
||||
2. Review hook output/logs
|
||||
3. Test hook independently
|
||||
4. Add error handling
|
||||
|
||||
### Performance Issues
|
||||
1. Profile hook execution time
|
||||
2. Optimize slow operations
|
||||
3. Consider async execution
|
||||
4. Cache results when possible
|
||||
|
||||
## Advanced Examples
|
||||
|
||||
See the example files in this directory:
|
||||
- `format-code.sh` - Auto-format code before writing
|
||||
- `pre-commit.sh` - Run tests before commits
|
||||
- `security-scan.sh` - Scan for security issues
|
||||
- `log-bash.sh` - Log all bash commands
|
||||
- `validate-prompt.sh` - Validate user prompts
|
||||
- `notify-team.sh` - Send notifications on events
|
||||
Edit `~/.claude/settings.json` or `.claude/settings.json` with the hook configuration shown above.
|
||||
|
||||
## Related Concepts
|
||||
|
||||
For more information on related Claude Code features, see:
|
||||
- **[Checkpoints and Rewind](../08-checkpoints/)** - Save and restore conversation state
|
||||
- **[Slash Commands](../01-slash-commands/)** - Create custom slash commands
|
||||
- **[Plugins](../07-plugins/)** - Bundled extension packages
|
||||
- **[Advanced Features](../09-advanced-features/)** - Explore advanced Claude Code capabilities
|
||||
- **[Main Concepts Guide](../claude_concepts_guide.md)** - Complete reference documentation
|
||||
|
||||
## Resources
|
||||
|
||||
- **Official Documentation**: [code.claude.com/docs/en/hooks](https://code.claude.com/docs/en/hooks)
|
||||
|
||||
Reference in New Issue
Block a user