Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when creating or publishing Claude Code hooks - covers executable format, event types, JSON I/O, exit codes, security requirements, and PRPM package structure
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 25% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 53% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 91% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 117% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 278% | 0% |
Use this skill when creating, improving, or publishing Claude Code hooks. Provides essential guidance on hook format, event handling, I/O conventions, and package structure.
Activate this skill when:
| Aspect | Requirement | | --------------- | ------------------------------------------------- | | Location | .claude/hooks/<event-name> | | Format | Executable file (shell, TypeScript, Python, etc.) | | Permissions | Must be executable (chmod +x) | | Shebang | Required (#!/bin/bash or #!/usr/bin/env node) | | Input | JSON via stdin | | Output | Text via stdout (shown to user) | | Exit Codes | 0 = success, 2 = block, other = error |
| Event | When It Fires | Common Use Cases | | -------------------- | --------------------------- | ---------------------------------------- | | session-start | New session begins | Environment setup, logging, checks | | user-prompt-submit | Before user input processes | Validation, enhancement, filtering | | tool-call | Before tool execution | Permission checks, logging, modification | | assistant-response | After assistant responds | Formatting, logging, cleanup |
Project hooks:
.claude/hooks/session-start
.claude/hooks/user-prompt-submitUser-global hooks:
~/.claude/hooks/session-start
~/.claude/hooks/tool-callEvery hook MUST:
bash#!/bin/bash # or #!/usr/bin/env node # or #!/usr/bin/env python3
bashchmod +x .claude/hooks/session-start
bash#!/bin/bash INPUT=$(cat) FILE=$(echo "$INPUT" | jq -r '.input.file_path // empty')
bashexit 0 # Success exit 2 # Block operation exit 1 # Error (logs but continues)
Hooks receive JSON via stdin with event-specific data:
json{ "event": "tool-call", "timestamp": "2025-01-15T10:30:00Z", "session_id": "abc123", "current_dir": "/path/to/project", "input": { "file_path": "/path/to/file.ts", "command": "npm test", "old_string": "...", "new_string": "..." } }
>&2) for errors| Code | Meaning | Behavior | | ------------ | ------- | -------------------------- | | 0 | Success | Continue normally | | 2 | Block | Stop operation, show error | | 1 or other | Error | Log error, continue |
Hooks should validate against the JSON schema:
Schema URL: https://github.com/pr-pm/prpm/blob/main/packages/converters/schemas/claude-hook.schema.json
Required frontmatter fields:
name - Hook identifier (lowercase, hyphens only)description - What the hook doesevent - Event type (optional, inferred from filename)language - bash, typescript, javascript, python, binary (optional)hookType: "hook" - For round-trip conversion| Mistake | Problem | Solution | | ---------------------- | ------------------------- | ------------------------------------ | | Not quoting variables | Breaks on spaces | Always use "$VAR" | | Missing shebang | Won't execute | Add #!/bin/bash | | Not executable | Permission denied | Run chmod +x hook-file | | Logging to stdout | Clutters transcript | Use stderr: echo "log" >&2 | | Wrong exit code | Doesn't block when needed | Use exit 2 to block | | No input validation | Security risk | Always validate JSON fields | | Slow operations | Blocks Claude | Run in background or use PostToolUse | | Absolute paths missing | Can't find scripts | Use $CLAUDE_PLUGIN_ROOT |
bash#!/bin/bash # .claude/hooks/session-start # Log session start echo "Session started at $(date)" >> ~/.claude/session.log # Check environment if ! command -v node &> /dev/null; then echo "Warning: Node.js not installed" >&2 fi # Output to user echo "Development environment ready" exit 0
typescript#!/usr/bin/env node // .claude/hooks/user-prompt-submit import { readFileSync } from 'fs'; // Read JSON from stdin const input = readFileSync(0, 'utf-8'); const data = JSON.parse(input); // Validate prompt if (data.prompt.includes('API_KEY')) { console.error('Warning: Prompt may contain secrets'); process.exit(2); // Block } console.log('Prompt validated'); process.exit(0);
Target < 100ms for PreToolUse hooks:
bash# Check dependencies exist if ! command -v jq &> /dev/null; then echo "jq not installed, skipping" >&2 exit 0 fi # Validate input FILE=$(echo "$INPUT" | jq -r '.input.file_path // empty') if [[ -z "$FILE" ]]; then echo "No file path provided" >&2 exit 1 fi
Always start with shebang:
bash#!/bin/bash #!/usr/bin/env node #!/usr/bin/env python3
bashBLOCKED=(".env" ".env.*" "*.pem" "*.key") for pattern in "${BLOCKED[@]}"; do case "$FILE" in $pattern) echo "Blocked: $FILE is sensitive" >&2 exit 2 ;; esac done
bash# WRONG - breaks on spaces prettier --write $FILE # RIGHT - handles spaces prettier --write "$FILE"
bashLOG_FILE=~/.claude-hooks/debug.log # Log to file echo "[$(date)] Processing $FILE" >> "$LOG_FILE" # Log to stderr (shows in transcript) echo "Hook running..." >&2
my-hook/
├── prpm.json # Package manifest
├── HOOK.md # Hook documentation
└── hook-script.sh # Hook executablejson{ "name": "@username/hook-name", "version": "1.0.0", "description": "Brief description shown in search", "author": "Your Name", "format": "claude", "subtype": "hook", "tags": ["automation", "security", "formatting"], "main": "HOOK.md" }
markdown--- name: session-logger description: Logs session start/end times for tracking event: SessionStart language: bash hookType: hook --- # Session Logger Hook Logs Claude Code session activity for tracking and debugging. ## Installation This hook will be installed to `.claude/hooks/session-start`. ## Behavior - Logs session start time to `~/.claude/session.log` - Displays environment status - Runs silent dependency checks ## Requirements - bash 4.0+ - write access to `~/.claude/` ## Source Code \`\`\`bash #!/bin/bash echo "Session started at $(date)" >> ~/.claude/session.log echo "Environment ready" exit 0 \`\`\`
bash# Test locally first prpm test # Publish to registry prpm publish # Version bumps prpm publish patch # 1.0.0 -> 1.0.1 prpm publish minor # 1.0.0 -> 1.1.0 prpm publish major # 1.0.0 -> 2.0.0
bash# Parse JSON safely INPUT=$(cat) if ! FILE=$(echo "$INPUT" | jq -r '.input.file_path // empty' 2>&1); then echo "JSON parse failed" >&2 exit 1 fi # Validate field exists [[ -n "$FILE" ]] || exit 1
bash# Prevent directory traversal if [[ "$FILE" == *".."* ]]; then echo "Path traversal detected" >&2 exit 2 fi # Keep in project directory if [[ "$FILE" != "$CLAUDE_PROJECT_DIR"* ]]; then echo "File outside project" >&2 exit 2 fi
Claude Code automatically:
| Feature | Hooks | Skills | Commands | | ------------ | ---------------------- | ---------------------- | --------------------- | | Format | Executable code | Markdown | Markdown | | Trigger | Automatic (events) | Automatic (context) | Manual (/command) | | Language | Any executable | N/A | N/A | | Use Case | Automation, validation | Reference, patterns | Quick tasks | | Security | Requires confirmation | No special permissions | Inherits from session |
Examples:
/review-pr quick code reviewBefore publishing:
chmod +x)Other measured skills in the registry, with their headline benchmark lift.