Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:48:43 +08:00
commit 2b34b5aa74
25 changed files with 7414 additions and 0 deletions

908
skills/hook-design/SKILL.md Normal file
View File

@@ -0,0 +1,908 @@
---
name: box-factory-hooks-design
description: Interpretive guidance for designing Claude Code hooks. Helps you understand hook lifecycle, when to use hooks vs other patterns, and common pitfalls. Use when creating or reviewing hooks.
---
# Hooks Design Skill
This skill provides interpretive guidance and best practices for creating Claude Code hooks. **ALWAYS fetch current official documentation before creating hooks** - this skill helps you understand what the docs mean and how to create excellent hooks.
## Required Reading Before Creating Hooks
Fetch these docs with WebFetch every time:
- **https://code.claude.com/docs/en/hooks** - Complete hook reference
## Core Understanding
### Hooks Are Deterministic Control
**Key insight:** Hooks provide guaranteed, deterministic execution at specific lifecycle events.
**What this means:**
- **Hooks** = Execute every single time (deterministic)
- **Prompts/Instructions** = Claude might forget (probabilistic)
- **Agents** = Context-dependent intelligence
- **Use hooks when "always" matters**
**Decision question:** Do you need this to happen every single time, or is "usually" okay?
**Examples:**
- Format after every file write → Hook
- Suggest code improvements → Prompt to Claude
- Run tests after code changes → Hook if mandatory, agent if discretionary
- Security validation before bash → Hook (must be enforced)
### Hook Architecture (How It Fits)
**Execution flow:**
```
User Input → Claude Thinks → Tool Execution
↑ ↓
Hooks fire here ────┘
```
**Critical implications:**
- **PreToolUse**: Can block/modify before tool runs
- **PostToolUse**: Can react after tool completes successfully
- **Stop**: Can't affect what Claude just did (it's done, only cleanup)
- **UserPromptSubmit**: Runs before Claude sees the prompt
### Exit Codes Are Communication (Official Specification)
**Exit 0**: Hook succeeded, continue execution
- stdout displays in transcript mode (CTRL-R)
- Exception: `UserPromptSubmit` and `SessionStart` where stdout becomes context for Claude
**Exit 2**: Blocking error, stop and handle
- stderr feeds to Claude for processing
- Behavior varies by event:
- **PreToolUse**: Blocks tool call
- **Stop/SubagentStop**: Blocks stoppage
- **UserPromptSubmit**: Blocks prompt, erases it, shows error to user only
**Other exit codes**: Non-blocking error
- stderr displays to user
- Execution continues
**Best practice:** Use exit 2 sparingly - it's powerful but disruptive. Use it for security/safety enforcement, not preferences.
## Hook Events (Official Specification)
Complete list of available events:
| Event | When It Fires | Matcher Applies |
|-------|--------------|----------------|
| **PreToolUse** | After Claude creates tool params, before processing | Yes |
| **PostToolUse** | Immediately after successful tool completion | Yes |
| **PermissionRequest** | When permission dialogs shown to users | No |
| **Notification** | When Claude Code sends notifications | No |
| **UserPromptSubmit** | When users submit prompts, before Claude processes | No |
| **Stop** | When main Claude agent finishes responding | No |
| **SubagentStop** | When subagents (Task tool calls) complete | No |
| **PreCompact** | Before compacting operations | No |
| **SessionStart** | When sessions start or resume | No |
| **SessionEnd** | When sessions terminate | No |
## Hook Types (Official Specification)
**Command Hooks** (`type: "command"`):
- Execute bash scripts with file system access
- Default timeout: 60 seconds (customizable per hook)
- Fast, deterministic operations
- Use for formatters, linters, file ops, git commands
**Prompt-Based Hooks** (`type: "prompt"`):
- Send queries to Claude Haiku for context-aware decisions
- Available for: `Stop`, `SubagentStop`, `UserPromptSubmit`, `PreToolUse`
- Use when judgment/context understanding needed
- Natural language evaluation
**Rule of thumb:** If you can write it as a bash script = command hook. If you need judgment = prompt hook.
## Hook Script Implementation Patterns (Best Practices)
### Bash vs Python for Hook Scripts
**Bash is ideal for:**
- Simple file operations (formatting, linting with external tools)
- Calling CLI tools directly
- Quick text processing with standard utilities
- Minimal logic, mostly orchestration
**Python is better for:**
- Complex validation logic
- JSON parsing and manipulation
- Advanced text processing
- Using Python libraries for analysis
- Multi-step processing with error handling
### Python Hook Scripts with UV (Best Practice)
For Python-based hooks requiring dependencies or complex logic, use UV's single-file script format with inline metadata. This provides self-contained, executable scripts without separate environment setup.
**When to use Python hooks:**
- Parsing complex JSON from stdin
- Advanced validation requiring libraries (AST analysis, schema validation)
- Multi-step processing beyond simple shell pipelines
- Need for structured error handling and reporting
**Pattern:** Use Skill tool: skill="box-factory:uv-scripts"
The uv-scripts skill provides complete patterns for creating Python hook scripts with inline dependencies, proper shebangs, and Claude Code integration.
**Quick example:**
```python
#!/usr/bin/env -S uv run --script
# /// script
# dependencies = ["ruff"]
# ///
import subprocess
import sys
import os
def main():
file_paths = os.environ.get("CLAUDE_FILE_PATHS", "").split()
if not file_paths:
sys.exit(0)
result = subprocess.run(["ruff", "check", *file_paths])
sys.exit(result.returncode)
if __name__ == "__main__":
main()
```
**Key advantages:**
- Self-contained (dependencies declared inline)
- No separate virtual environment management
- Executable directly with proper shebang
- Fast startup with UV's performance
- Perfect for plugin hooks (ships with dependencies)
## Matcher Syntax (Official Specification)
Matchers specify which tools trigger hooks (applies to PreToolUse and PostToolUse only):
**Exact matching:**
```json
"matcher": "Write"
```
**Regex patterns with pipe:**
```json
"matcher": "Edit|Write"
"matcher": "Notebook.*"
```
**Wildcard (match all):**
```json
"matcher": "*"
```
**Empty matcher:**
Omit for events like `UserPromptSubmit` that don't apply to specific tools.
**Note:** Matchers are case-sensitive.
## Configuration Structure (Official Specification)
Located in `~/.claude/settings.json`, `.claude/settings.json`, or `.claude/settings.local.json`:
```json
{
"hooks": {
"EventName": [
{
"matcher": "ToolPattern",
"hooks": [
{
"type": "command",
"command": "bash-command",
"timeout": 30
}
]
}
]
}
}
```
**Timeout field:** Optional, specified in seconds (default 60).
## Hook Input (stdin) - Official Specification
All hooks receive JSON via stdin:
**Base structure (all events):**
```json
{
"session_id": "string",
"transcript_path": "path/to/transcript.jsonl",
"cwd": "current/working/directory",
"permission_mode": "default|plan|acceptEdits|bypassPermissions",
"hook_event_name": "EventName"
}
```
**Event-specific fields:**
- **PreToolUse/PostToolUse**: Adds `tool_name`, `tool_input`
- **UserPromptSubmit**: Adds `prompt`
- Other events may include additional context
**Best practice:** Parse stdin JSON to access context, don't rely only on environment variables.
## Hook Output (stdout) - Official Specification
Two approaches for returning results:
### Simple Exit Code Approach
Just use exit codes and stderr for errors. Most common for straightforward hooks.
### Advanced JSON Output
Return structured JSON for sophisticated control:
```json
{
"continue": true,
"stopReason": "Custom message",
"suppressOutput": true,
"systemMessage": "Warning to display",
"hookSpecificOutput": {
"hookEventName": "EventName",
"additionalContext": "string"
}
}
```
### PostToolUse Communication Pattern (CRITICAL)
**Key insight:** PostToolUse hooks have two output channels with different visibility:
**For messages visible DIRECTLY to users (no verbose mode required):**
Use `systemMessage` field - displays immediately to users:
```json
{
"systemMessage": "Markdown formatted: path/to/file.md"
}
```
**For messages visible ONLY to Claude (user must enable verbose mode):**
Use `additionalContext` in `hookSpecificOutput`:
```json
{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "Internal context for Claude's awareness"
}
}
```
**Complete output pattern:**
```python
import json
output = {
"systemMessage": "Formatted successfully: file.md", # Shows to user directly
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "Additional context for Claude" # Only in verbose mode
}
}
print(json.dumps(output), flush=True)
sys.exit(0)
```
**Common mistake:** Using only `additionalContext` when user feedback is needed. This requires users to enable verbose mode (CTRL-O) to see output.
**Correct pattern:**
- **User feedback needed:** Use `systemMessage` (visible immediately)
- **Claude context only:** Use `additionalContext` (verbose mode only)
- **Both:** Include both fields in the JSON output
- **Blocking errors:** Use exit 2 with stderr (rare, security/safety only)
### PreToolUse Special Output
For modifying or blocking tool execution:
```json
{
"permissionDecision": "allow|deny|ask",
"updatedInput": {
"modified": "tool parameters"
}
}
```
**Use case:** Modify tool inputs before execution (e.g., add safety flags to bash commands).
## Environment Variables (Official Specification)
Available in command hooks:
| Variable | Purpose |
|----------|---------|
| `$CLAUDE_PROJECT_DIR` | Absolute path to project root |
| `$CLAUDE_ENV_FILE` | File path for persisting env vars (SessionStart only) |
| `${CLAUDE_PLUGIN_ROOT}` | Plugin directory path (for plugin hooks) |
| `$CLAUDE_CODE_REMOTE` | `"true"` for remote, empty for local execution |
**Best practice:** Always quote variables: `"$CLAUDE_PROJECT_DIR"` not `$CLAUDE_PROJECT_DIR`
## Decision Framework
### Hook vs Agent vs Command
**Use Hook when:**
- Need guaranteed execution every time
- Simple, deterministic rule (format, lint, validate)
- Integrating with external tools
- Performance/safety enforcement
- Must happen at specific lifecycle event
**Use Agent when:**
- Complex decision-making involved
- Need Claude's intelligence for analysis
- Context-dependent logic
- Natural language processing needed
- Can be triggered proactively by context
**Use Command (Slash Command) when:**
- User wants explicit control over when it runs
- Not tied to lifecycle events
- One-off operations
## Common Use Patterns (Best Practices)
### SessionStart Pattern
**Purpose:** Initialize session state, inject context
```json
{
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "cat .claude/project-context.md"
}
]
}
]
}
```
**Key:** stdout becomes Claude's context. Use to load project guidelines, conventions, or state.
### UserPromptSubmit Pattern
**Purpose:** Validate or enrich prompts before Claude sees them
```json
{
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/inject-security-reminders.sh"
}
]
}
]
}
```
**Key:** stdout goes to Claude. Can add context or use exit 2 to block prompts.
### PreToolUse Pattern
**Purpose:** Validate or modify before execution
```json
{
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/security-check.sh"
}
]
}
]
}
```
**Power move:** Exit 2 to block dangerous commands and explain why to Claude.
### PostToolUse Pattern
**Purpose:** React to successful tool completion
```json
{
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "prettier --write \"$CLAUDE_FILE_PATHS\" 2>/dev/null || true"
}
]
}
]
}
```
**Common uses:** Format code, run linters, update documentation, cleanup.
**CRITICAL for PostToolUse:** To communicate results to users, hooks must output JSON to stdout with `systemMessage`:
```python
#!/usr/bin/env -S uv run --quiet --script
# /// script
# dependencies = []
# ///
import json
import sys
def output_json_response(system_message=None, additional_context=None):
"""Output JSON response for Claude to process."""
response = {}
if system_message:
response["systemMessage"] = system_message # Visible directly to user
if additional_context:
response["hookSpecificOutput"] = {
"hookEventName": "PostToolUse",
"additionalContext": additional_context # Only visible in verbose mode
}
print(json.dumps(response), flush=True)
# Read hook input from stdin
hook_input = json.load(sys.stdin)
file_path = hook_input.get("tool_input", {}).get("file_path")
# Run linter/formatter
# ...
# Communicate result directly to user
output_json_response(system_message=f"Formatted successfully: {file_path}")
sys.exit(0)
```
**Common mistakes:**
- Using only `additionalContext` when user feedback is needed (requires verbose mode)
- Writing to stderr instead of JSON stdout (completely invisible)
### Stop Pattern
**Purpose:** Session cleanup, final actions
```json
{
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/auto-commit.sh"
}
]
}
]
}
```
**Note:** Claude already responded, can't change that. Use for cleanup, notifications, test runs.
## Common Pitfalls (Best Practices)
### Pitfall #1: Blocking Everything
**Problem:** Overly aggressive hook blocks all operations
```json
{
"PreToolUse": [
{
"matcher": "*",
"hooks": [{"type": "command", "command": "exit 2"}]
}
]
}
```
**Result:** Claude can't do anything, unusable.
**Better:** Selective blocking with clear criteria for security/safety only.
### Pitfall #2: Slow Hooks
**Problem:** Hook takes 30+ seconds, blocks user experience
```bash
npm install # Slow, blocking
exit 0
```
**Impact:** Claude waits, terrible UX.
**Better:** Fast validation or background execution
```bash
npm outdated | head -5 # Quick check
exit 0
```
**Or:** Adjust timeout for legitimately long operations:
```json
{
"type": "command",
"command": "long-running-task.sh",
"timeout": 120
}
```
### Pitfall #3: Silent Failures
**Problem:** Errors disappear into the void
```bash
important-check || true
exit 0
```
**Result:** User never knows check failed.
**Better:** Clear error communication
```bash
if ! important-check; then
echo "Check failed: [specific reason]" >&2
exit 1 # Non-blocking, but visible
fi
exit 0
```
### Pitfall #4: Assuming User Interaction
**Problem:** Hook expects user input
```bash
read -p "Confirm? " response
exit 0
```
**Result:** Hook hangs indefinitely (no user to respond).
**Better:** Fully automated decisions based on stdin JSON or environment.
### Pitfall #5: Ignoring Security
**Problem:** Hook doesn't validate inputs, vulnerable to path traversal
```bash
cat "$SOME_PATH" # Dangerous if not validated
```
**Result:** Could access sensitive files outside project.
**Better:** Validate and sanitize
```bash
if [[ "$SOME_PATH" == *".."* ]]; then
echo "Path traversal detected" >&2
exit 2
fi
# Continue safely
```
**Official guidance:** Skip sensitive files (`.env`, `.git/`, credentials). Validate inputs from stdin.
### Pitfall #6: Not Quoting Variables
**Problem:** Unquoted shell variables break with spaces
```bash
prettier --write $CLAUDE_FILE_PATHS # Breaks if path has spaces
```
**Better:** Always quote variables
```bash
prettier --write "$CLAUDE_FILE_PATHS"
```
### Pitfall #7: PostToolUse Using Wrong Output Field
**Problem:** Hook uses `additionalContext` when user feedback is needed
```python
# Wrong - Only visible in verbose mode (CTRL-O)
import json
output = {
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "Formatted successfully: file.md"
}
}
print(json.dumps(output), flush=True)
sys.exit(0)
```
**Result:** User must enable verbose mode to see feedback.
**Better:** Use `systemMessage` for direct user visibility
```python
# Correct - Visible immediately to user
import json
output = {
"systemMessage": "Formatted successfully: file.md"
}
print(json.dumps(output), flush=True)
sys.exit(0)
```
**Why:**
- `systemMessage` displays directly to users (no verbose mode required)
- `additionalContext` only visible in verbose mode (CTRL-O) or as Claude's context
- stderr output is only for blocking errors (exit 2)
## Security Considerations (Official Guidance)
**Critical warning from docs:** "Claude Code hooks execute arbitrary shell commands on your system automatically."
**Implications:**
- Hooks can access/modify any files your user account permits
- You bear sole responsibility for configured commands
- Test thoroughly in safe environments first
- Review hooks from untrusted sources carefully
**Protection mechanism:**
- Configuration snapshots captured at startup
- Hook changes require review via `/hooks` menu
- Prevents mid-session malicious modifications
**Best practices:**
1. Validate and sanitize all inputs from stdin
2. Block path traversal (`..` in paths)
3. Use absolute paths with `$CLAUDE_PROJECT_DIR`
4. Skip sensitive files (`.env`, credentials, `.git/`)
5. For prompt-based hooks, be specific about criteria
6. Set appropriate timeouts
7. Test in isolated environments first
## Debugging Hooks (Best Practices)
**View hook execution:**
Press **CTRL-R** in Claude Code to see:
- Hook stdout/stderr
- Execution flow
- Exit codes
- Timing information
**Add logging to hooks:**
```bash
echo "Hook triggered: $(date)" >> ~/.claude/hook-log.txt
echo "Input: $SOME_VAR" >> ~/.claude/hook-log.txt
# Continue with hook logic
exit 0
```
**Parse stdin for debugging:**
```bash
# Save stdin to debug
cat > /tmp/hook-debug.json
cat /tmp/hook-debug.json | jq '.' # Pretty print
exit 0
```
## Advanced Features (Official Specification)
### Modifying Tool Inputs (PreToolUse)
Return JSON to modify tool parameters:
```bash
#!/bin/bash
# Read stdin
INPUT=$(cat)
# Add safety flag to bash commands
MODIFIED=$(echo "$INPUT" | jq '.tool_input.command = .tool_input.command + " --safe-mode"')
# Return modified input
echo "$MODIFIED" | jq '{permissionDecision: "allow", updatedInput: .tool_input}'
exit 0
```
### Custom Timeouts
Adjust timeout per hook:
```json
{
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "./long-build.sh",
"timeout": 300
}
]
}
]
}
```
### Prompt-Based Intelligence
Use Claude Haiku for context-aware decisions:
```json
{
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "prompt",
"command": "Analyze this bash command for security risks. If dangerous, explain why and recommend safer alternative."
}
]
}
]
}
```
## Plugin Integration
When creating hooks for plugins:
**Structure:**
```
my-plugin/
├── .claude-plugin/plugin.json
└── hooks/
└── hooks.json
```
**Reference plugin root:**
```bash
"${CLAUDE_PLUGIN_ROOT}/scripts/hook-script.sh"
```
See plugin-design skill for complete plugin context.
## Hook Quality Checklist
Before deploying hooks:
**Functionality (from official docs):**
- ✓ Correct event type for use case
- ✓ Valid matcher pattern (if applicable)
- ✓ Proper JSON structure in settings
- ✓ Appropriate timeout configured
**Quality (best practices):**
- ✓ Fast execution (< 60s or custom timeout)
- ✓ Clear error messages to stderr
- ✓ Appropriate exit codes (0, 2, other)
- ✓ No user interaction required
- ✓ Variables quoted properly
- ✓ Inputs validated/sanitized
**Security (best practices):**
- ✓ Path traversal blocked
- ✓ Sensitive files skipped
- ✓ Absolute paths used
- ✓ No secret exposure
- ✓ Tested in safe environment
## Example: High-Quality Hook
**Basic (hypothetical docs example):**
```json
{
"PostToolUse": [
{
"matcher": "Write",
"hooks": [{"type": "command", "command": "prettier --write"}]
}
]
}
```
**Issues:**
- ❌ Missing file path variable
- ❌ No error handling
- ❌ Doesn't catch Edit operations
**Excellent (applying best practices):**
```json
{
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "prettier --write \"$CLAUDE_FILE_PATHS\" 2>/dev/null || true",
"timeout": 30
}
]
}
]
}
```
**Improvements:**
- ✅ Uses `$CLAUDE_FILE_PATHS` variable
- ✅ Quoted variable for spaces
- ✅ Error suppression (|| true) prevents blocking
- ✅ Catches both Write and Edit
- ✅ Custom timeout for faster failures
- ✅ Redirects stderr to avoid noise
## Documentation References
These are the authoritative sources. Fetch them before creating hooks:
**Core specifications:**
- https://code.claude.com/docs/en/hooks - Complete hook reference
**Related topics:**
- See agent-design skill for when to use agents instead
- See slash-command-design skill for user-triggered operations
- See plugin-design skill for packaging hooks in plugins
- See uv-scripts skill for Python-based hook implementation patterns
**Remember:** Official docs provide structure and features. This skill provides best practices and patterns for creating excellent hooks.