Back to Skills
Claude Hook Writer
Expert guidance for writing secure, reliable, and performant Claude Code hooks - validates design decisions, enforces best practices, and prevents common pitfalls. Use when creating, reviewing, or debugging Claude Code hooks.
By secondsky
Skill Content
# Claude Hook Writer
**Status**: Production Ready
**Version**: 2.0.0 (Optimized with progressive disclosure)
**Last Updated**: 2025-12-17
---
## Overview
Expert guidance for writing secure, reliable, and performant Claude Code hooks. This skill validates design decisions, enforces best practices, and prevents common pitfalls.
---
## When to Use This Skill
- Designing a new Claude Code hook
- Reviewing existing hook code
- Debugging hook failures
- Optimizing slow hooks
- Securing hooks that handle sensitive data
- Publishing hooks as PRPM packages
---
## Core Principles
### 1. Security is Non-Negotiable
Hooks execute automatically with user permissions and can read, modify, or delete any file the user can access.
**ALWAYS validate and sanitize all input.** Hooks receive JSON via stdin—never trust it blindly.
**For complete security patterns**: Load `references/security-requirements.md` when implementing validation or securing hooks.
### 2. Reliability Over Features
A hook that works 99% of the time is a broken hook. Edge cases (Unicode filenames, spaces in paths, missing tools) will happen.
**Test with edge cases before deploying.**
**For reliability patterns**: Load `references/reliability-performance.md` when handling errors or edge cases.
### 3. Performance Matters
Hooks block operations. A 5-second hook means Claude waits 5 seconds before continuing.
**Keep hooks fast. Run heavy operations in background.**
**For performance optimization**: Load `references/reliability-performance.md` when optimizing hook speed.
### 4. Fail Gracefully
Missing dependencies, malformed input, and disk errors will occur.
**Handle errors explicitly. Log failures. Return meaningful exit codes.**
---
## Hook Design Checklist
Before writing code, answer these questions:
### What Event Does This Hook Target?
- `PreToolUse` - Before tool execution (modify input, validate, block)
- `PostToolUse` - After tool completes (format, log, cleanup)
- `UserPromptSubmit` - Before user input processes (validate, enhance)
- `SessionStart` - When Claude Code starts (setup, env check)
- `SessionEnd` - When Claude Code exits (cleanup, persist state)
- `Notification` - During alerts (desktop notifications, logging)
- `Stop` / `SubagentStop` - When responses finish (cleanup, summary)
- `PreCompact` - Before context compaction (save important context)
**Common mistake:** Using PostToolUse for validation (too late—tool already ran). Use PreToolUse to block operations.
### Which Tools Should Trigger This Hook?
Be specific. `matcher: "*"` runs on every tool call.
**Good matchers:**
- `"Write"` - Only file writes
- `"Edit|Write"` - File modifications
- `"Bash"` - Shell commands
- `"mcp__github__*"` - All GitHub MCP tools
**Bad matchers:**
- `"*"` - Everything (use only for logging/metrics)
### What Input Does This Hook Need?
Different tools provide different input. Check what's available:
```bash
# PreToolUse / PostToolUse
{
"input": {
"file_path": "/path/to/file.ts", // Read, Write, Edit
"command": "npm test", // Bash
"old_string": "...", // Edit
"new_string": "..." // Edit
}
}
```
**Validate fields exist before using them:**
```bash
FILE=$(echo "$INPUT" | jq -r '.input.file_path // empty')
if [[ -z "$FILE" ]]; then
echo "No file path provided" >&2
exit 1
fi
```
### Should This Be a Command Hook or Prompt Hook?
**Command hooks** (`type: "command"`):
- Fast (milliseconds)
- Deterministic
- Good for: formatting, logging, file checks
**Prompt hooks** (`type: "prompt"`):
- Slow (2-10 seconds)
- Context-aware (uses LLM)
- Good for: complex validation, security analysis, intent detection
**Rule of thumb:** Use command hooks unless you need LLM reasoning.
### What Exit Code Communicates Success/Failure?
- `exit 0` - Success (continue operation)
- `exit 2` - Block operation (show error to Claude)
- `exit 1` or other - Non-blocking error (log but continue)
**For PreToolUse hooks:**
- Exit 2 blocks the tool from running
- Exit 0 allows it (optionally with modified input)
**For PostToolUse hooks:**
- Exit codes don't block (tool already ran)
- Use exit 0 for success, 1 for logging errors
---
## Top 5 Pitfalls (Must Know)
### Pitfall #1: Not Quoting Variables
**Error**: Hooks break on filenames with spaces or special characters
**Why**: Unquoted variables split on whitespace
**Example**:
```bash
# ❌ WRONG - breaks on "my file.txt"
cat $FILE
prettier --write $FILE
rm $FILE
# ✅ RIGHT - handles spaces and special chars
cat "$FILE"
prettier --write "$FILE"
rm "$FILE"
```
**Why this matters**: Files with spaces (`"my file.txt"`), Unicode (`"文件.txt"`), or special chars (`"file (1).txt"`) are common.
**For quoting best practices**: Load `references/security-requirements.md` for comprehensive input handling patterns.
---
### Pitfall #2: Trusting Input Without Validation
**Error**: Hook executes on malicious or malformed input
**Why**: Not validating JSON fields before using them
**Example**:
```bash
# ❌ DANGEROUS - no validation
FILE=$(jq -r '.input.file_path')
rm "$FILE" # Could delete ../../../etc/passwd
# ✅ SAFE - validate first
FILE=$(jq -r '.input.file_path // empty')
[[ -n "$FILE" ]] || exit 1
[[ "$FILE" == "$CLAUDE_PROJECT_DIR"* ]] || exit 2
[[ "$FILE" != *".."* ]] || exit 2
rm "$FILE"
```
**Why this matters**: Prevents path traversal attacks, protects files outside project, prevents malformed input crashes.
**For complete security patterns**: Load `references/security-requirements.md`.
---
### Pitfall #3: Blocking Operations Too Long
**Error**: Hook takes 30+ seconds, blocking Claude
**Why**: Running expensive operations (tests, builds) synchronously in hook
**Example**:
```bash
# ❌ BLOCKS Claude for 30 seconds
npm test
npm run build
# ✅ RUN IN BACKGROUND - returns immediately
(npm test > /tmp/test-results.log 2>&1 &)
(npm run build > /tmp/build.log 2>&1 &)
exit 0
```
**Why this matters**: Slow hooks create bad user experience. Target < 100ms for PreToolUse, < 500ms for PostToolUse.
**For performance optimization**: Load `references/reliability-performance.md`.
---
### Pitfall #4: Wrong Exit Code for Blocking
**Error**: PreToolUse hook doesn't actually block the operation
**Why**: Using exit 1 instead of exit 2
**Example**:
```bash
# ❌ WRONG - logs error but doesn't block
if [[ $FILE == ".env" ]]; then
echo "Don't edit .env" >&2
exit 1 # Tool still runs!
fi
# ✅ RIGHT - actually blocks
if [[ $FILE == ".env" ]]; then
echo "Blocked: .env is protected" >&2
exit 2 # Tool is blocked
fi
```
**Why this matters**: Exit 1 only logs errors. Exit 2 is required to block in PreToolUse hooks.
**For exit code patterns**: Load `references/hook-templates.md` for complete hook response patterns.
---
### Pitfall #5: Assuming Tools Exist
**Error**: Hook crashes when dependency is missing
**Why**: Not checking if tool is installed before using
**Example**:
```bash
# ❌ BREAKS if prettier not installed
prettier --write "$FILE"
# ✅ SAFE - check first
if command -v prettier &>/dev/null; then
prettier --write "$FILE"
else
echo "prettier not installed, skipping" >&2
exit 0 # Success exit, just skip
fi
```
**Why this matters**: Users may not have all tools installed. Hooks should degrade gracefully.
**For reliability patterns**: Load `references/reliability-performance.md`.
---
## Critical Rules
### Always Do
✅ Validate all JSON input before using (`jq -r '... // empty'`)
✅ Quote all variables containing paths or user input
✅ Use absolute paths for scripts (`${CLAUDE_PLUGIN_ROOT}/...`)
✅ Block sensitive files (`.env`, `*.key`, credentials)
✅ Check if required tools exist (`command -v toolname`)
✅ Set reasonable timeouts (< 5s for PreToolUse)
✅ Run heavy operations in background
✅ Test with edge cases (spaces, Unicode, special chars)
✅ Use exit 2 to block in PreToolUse hooks
✅ Log errors to stderr or file, not stdout
### Never Do
❌ Trust JSON input without validation
❌ Use unquoted variables ($FILE instead of "$FILE")
❌ Use relative paths for scripts
❌ Skip path sanitization (check for `..`, validate in project)
❌ Assume tools are installed
❌ Block for > 1 second in PreToolUse hooks
❌ Use exit 1 when you mean to block (use exit 2)
❌ Log sensitive data to stdout or files
❌ Use `matcher: "*"` unless truly necessary
---
## When to Load References
Load reference files when working on specific hook aspects:
### Security Requirements (`references/security-requirements.md`)
Load when:
- Implementing input validation and sanitization
- Securing hooks that handle sensitive data
- Blocking sensitive files (`.env`, keys, credentials)
- Preventing path traversal attacks
- Understanding security vulnerabilities and best practices
- Testing security with malicious input
### Reliability & Performance (`references/reliability-performance.md`)
Load when:
- Handling missing dependencies or tools
- Setting timeouts and handling slow operations
- Optimizing hook performance (< 100ms target)
- Running heavy operations in background
- Caching expensive results
- Testing with edge cases (Unicode, spaces, deep paths)
- Deduplicating expensive operations
### Code Templates (`references/code-templates.md`)
Load when:
- Starting a new hook and need working examples
- Implementing format-on-save functionality
- Blocking sensitive files from modification
- Logging commands or operations
- Using prompt-based security analysis
- Customizing templates for specific use cases
### Testing & Debugging (`references/testing-debugging.md`)
Load when:
- Writing test cases for hooks
- Debugging hook failures or unexpected behavior
- Testing with edge cases (malformed JSON, missing fields)
- Checking hook execution in transcript (Ctrl-R)
- Profiling hook performance
- Creating automated test suites
### Publishing Guide (`references/publishing-guide.md`)
Load when:
- Publishing hooks to PRPM registry
- Creating package manifest (prpm.json)
- Configuring hook.json with advanced options
- Using `continue`, `stopReason`, `suppressOutput`, `systemMessage`
- Writing README.md for users
- Understanding versioning and publishing commands
### Quick Reference (`references/quick-reference.md`)
Load when:
- Need quick syntax lookup (exit codes, jq patterns)
- Looking up environment variables
- Finding common bash patterns (file validation, background execution)
- Checking hook events and matchers
- Need performance tips summary
- Looking up JSON input structure
---
## Final Checklist
Before publishing a hook:
- [ ] Validates all stdin input with jq
- [ ] Quotes all variables
- [ ] Uses absolute paths for scripts
- [ ] Blocks sensitive files (`.env`, `*.key`, etc.)
- [ ] Handles missing tools gracefully
- [ ] Sets reasonable timeout (< 5s for PreToolUse)
- [ ] Logs errors to stderr or file, not stdout
- [ ] Tests with edge cases (spaces, Unicode, malformed JSON)
- [ ] Tests in real Claude Code session
- [ ] Documents dependencies in README
- [ ] Uses semantic versioning
- [ ] Clear description and tags
---
## Using Bundled Resources
This skill includes 6 reference files for on-demand loading:
**Security & Reliability** (2 files):
- `security-requirements.md` - Input validation, path sanitization, blocking sensitive files
- `reliability-performance.md` - Error handling, timeouts, performance optimization
**Implementation** (2 files):
- `code-templates.md` - Working hook examples (format-on-save, block-sensitive, logger, etc.)
- `quick-reference.md` - Fast syntax lookup (exit codes, jq patterns, environment vars)
**Testing & Publishing** (2 files):
- `testing-debugging.md` - Test patterns, edge cases, debugging techniques
- `publishing-guide.md` - PRPM packaging, advanced configuration, README template
Load references on-demand when specific knowledge is needed. See "When to Load References" section for triggers.
---
## Resources
- [Claude Code Hooks Docs](https://docs.claude.com/en/docs/claude-code/hooks)
- [PRPM Hook Packages](https://prpm.dev/packages?format=claude&subtype=hook)
---
**Last verified**: 2025-12-17 | **Version**: 2.0.0How to use
- Copy the skill content above
- Create a .claude/skills directory in your project
- Save as .claude/skills/claude-skills-claude-hook-writer.md
- Use /claude-skills-claude-hook-writer in Claude Code to invoke this skill