Agent skill
claude-hooks
This skill should be used when creating hooks, automating workflows, or when "PreToolUse", "PostToolUse", "hooks.json", "event handler", or "create hook" are mentioned.
Install this agent skill to your Project
npx add-skill https://github.com/outfitter-dev/agents/tree/main/plugins/outfitter/skills/claude-hooks
Metadata
Additional technical details for this skill
- version
- 2.0.0
- related skills
-
[ "claude-commands", "claude-plugins", "claude-agents", "claude-config" ]
SKILL.md
Claude Hook Authoring
Create event hooks that automate workflows, validate operations, and respond to Claude Code events.
Hook Types
Three hook execution types:
| Type | Best For | Example |
|---|---|---|
| command | Deterministic checks, external tools, performance | Bash script validates paths |
| prompt | Complex reasoning, context-aware validation | LLM evaluates if action is safe |
| agent | Multi-step verification requiring tool access | Agent with Read/Grep tools verifies consistency |
Command hooks (for deterministic/fast checks):
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/validate.sh",
"timeout": 10
}
Prompt hooks (recommended for complex logic):
{
"type": "prompt",
"prompt": "Evaluate if this file write is safe: $TOOL_INPUT. Check for sensitive paths, credentials, path traversal. Return 'allow' or 'deny' with reason.",
"timeout": 30
}
Agent hooks (for complex multi-step verification):
{
"type": "agent",
"prompt": "Verify this code change maintains consistency with the existing codebase. Check imports, type signatures, and naming conventions. Use Read and Grep tools as needed.",
"allowedTools": ["Read", "Grep", "Glob"],
"timeout": 120
}
Agent hooks spawn a subagent with tool access for verification tasks that require reading files, searching code, or multi-step reasoning. Use when prompt hooks are insufficient.
Hook Events
| Event | When | Can Block | Common Uses |
|---|---|---|---|
| PreToolUse | Before tool executes | Yes | Validate commands, check paths, enforce policies |
| PostToolUse | After tool succeeds | No | Auto-format, run linters, update docs |
| PostToolUseFailure | After tool fails | No | Error logging, retry logic, notifications |
| PermissionRequest | Permission dialog shown | Yes | Auto-allow/deny based on rules |
| UserPromptSubmit | User submits prompt | No | Add context, log activity, augment prompts |
| Notification | Claude sends notification | No | External alerts, logging |
| Stop | Main agent finishes | No | Cleanup, completion notifications |
| SubagentStart | Subagent spawns | No | Track subagent usage |
| SubagentStop | Subagent finishes | No | Log results, trigger follow-ups |
| Setup | --init, --init-only, or --maintenance flags |
No | Initialize environment, install dependencies |
| PreCompact | Before context compacts | No | Backup conversation, preserve context |
| SessionStart | Session starts/resumes | No | Load context, show status, init resources |
| SessionEnd | Session ends | No | Cleanup, save state, log metrics |
See references/hook-types.md for detailed documentation of each event.
Quick Start
Auto-Format TypeScript
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit(*.ts|*.tsx)",
"hooks": [{
"type": "command",
"command": "biome check --write \"$file\"",
"timeout": 10
}]
}
]
}
}
Block Dangerous Commands
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.sh",
"timeout": 5
}]
}
]
}
}
validate-bash.sh:
#!/usr/bin/env bash
set -euo pipefail
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if echo "$COMMAND" | grep -qE '\brm\s+-rf\s+/'; then
echo "Dangerous command blocked: rm -rf /" >&2
exit 2 # Exit 2 = block and show error to Claude
fi
exit 0
Smart Validation with Prompt Hook
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [{
"type": "prompt",
"prompt": "Analyze this file operation for safety. Check: 1) No sensitive paths (/etc, ~/.ssh), 2) No credentials in content, 3) No path traversal (..). Tool input: $TOOL_INPUT. Respond with JSON: {\"decision\": \"allow|deny\", \"reason\": \"...\"}",
"timeout": 30
}]
}
]
}
}
Configuration Locations
| Location | Scope | Committed |
|---|---|---|
.claude/settings.json |
Project (team-shared) | Yes |
.claude/settings.local.json |
Project (local only) | No |
~/.claude/settings.json |
Personal (all projects) | No |
plugin/hooks/hooks.json |
Plugin | Yes |
Plugin Format (hooks.json)
Uses wrapper structure:
{
"description": "Plugin hooks for auto-formatting",
"hooks": {
"PostToolUse": [...]
}
}
Settings Format (settings.json)
Direct structure (no wrapper):
{
"hooks": {
"PostToolUse": [...]
}
}
Matchers
Matchers determine which tool invocations trigger the hook. Case-sensitive.
{"matcher": "Write"} // Exact match
{"matcher": "Edit|Write"} // Multiple tools (OR)
{"matcher": "*"} // All tools
{"matcher": "Write(*.py)"} // File pattern
{"matcher": "Write|Edit(*.ts|*.tsx)"} // Multiple + pattern
{"matcher": "mcp__memory__.*"} // MCP server tools
{"matcher": "mcp__github__create_issue"} // Specific MCP tool
Lifecycle hooks (SessionStart, SessionEnd, Stop, Notification) use special matchers:
// SessionStart matchers
{"matcher": "startup"} // Initial start
{"matcher": "resume"} // --resume or --continue
{"matcher": "clear"} // After /clear
{"matcher": "compact"} // After compaction
// PreCompact matchers
{"matcher": "manual"} // User triggered /compact
{"matcher": "auto"} // Automatic compaction
See references/matchers.md for advanced patterns.
Input Format
All hooks receive JSON on stdin:
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.jsonl",
"cwd": "/current/working/directory",
"hook_event_name": "PreToolUse",
"permission_mode": "ask",
"tool_name": "Write",
"tool_input": {
"file_path": "/project/src/file.ts",
"content": "export const foo = 'bar';"
}
}
Event-specific fields:
- Tool hooks:
tool_name,tool_input,tool_result(PostToolUse) - UserPromptSubmit:
user_prompt - Stop/SubagentStop:
reason
Prompt hooks access fields via placeholders:
$ARGUMENTS- Full context passed to the hook (general-purpose)$TOOL_INPUT- Tool input for tool-related events$TOOL_RESULT- Tool result (PostToolUse only)$USER_PROMPT- User prompt (UserPromptSubmit only)
Reading Input
Bash:
#!/usr/bin/env bash
set -euo pipefail
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
Bun/TypeScript:
#!/usr/bin/env bun
const input = await Bun.stdin.json();
const toolName = input.tool_name;
const filePath = input.tool_input?.file_path;
Output Format
Exit Codes (Simple)
exit 0 # Success, continue execution
exit 2 # Block operation (PreToolUse only), stderr shown to Claude
exit 1 # Warning, stderr shown to user, continues
JSON Output (Advanced)
{
"continue": true,
"suppressOutput": false,
"systemMessage": "Context for Claude",
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow|deny|ask",
"permissionDecisionReason": "Explanation",
"updatedInput": {"modified": "field"}
}
}
PreToolUse can modify tool input via updatedInput and control permissions via permissionDecision.
Environment Variables
| Variable | Availability | Description |
|---|---|---|
$CLAUDE_PROJECT_DIR |
All hooks | Project root directory |
$CLAUDE_PLUGIN_ROOT |
Plugin hooks | Plugin root (use for portable paths) |
$file |
PostToolUse (Write/Edit) | Path to affected file |
$CLAUDE_ENV_FILE |
SessionStart | Write env vars here to persist |
$CLAUDE_CODE_REMOTE |
All hooks | Set if running in remote context |
Plugin hooks should always use ${CLAUDE_PLUGIN_ROOT} for portability:
{
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/validate.sh"
}
SessionStart can persist environment variables:
#!/usr/bin/env bash
# Persist variables for the session
echo "export PROJECT_TYPE=nodejs" >> "$CLAUDE_ENV_FILE"
echo "export API_URL=https://api.example.com" >> "$CLAUDE_ENV_FILE"
Component-Scoped Hooks
Skills, agents, and commands can define hooks in frontmatter. These hooks only run when the component is active.
Supported events: PreToolUse, PostToolUse, Stop
Skill with Hooks
---
name: my-skill
description: Skill with validation hooks
hooks:
PreToolUse:
- matcher: "Write|Edit"
hooks:
- type: prompt
prompt: "Validate this write operation for the skill context..."
---
Agent with Hooks
---
name: security-reviewer
model: sonnet
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "${CLAUDE_PLUGIN_ROOT}/scripts/validate-bash.sh"
Stop:
- matcher: "*"
hooks:
- type: prompt
prompt: "Verify the security review is complete..."
---
Execution Model
Parallel execution: All matching hooks run in parallel, not sequentially.
{
"PreToolUse": [{
"matcher": "Write",
"hooks": [
{"type": "command", "command": "check1.sh"}, // Runs in parallel
{"type": "command", "command": "check2.sh"}, // Runs in parallel
{"type": "prompt", "prompt": "Validate..."} // Runs in parallel
]
}]
}
Implications:
- Hooks cannot see each other's output
- Non-deterministic ordering
- Design for independence
Hot-swap limitations: Hook changes require restarting Claude Code. Editing hooks.json or hook scripts does not affect the current session.
Security Best Practices
- Validate all input - Check for path traversal, sensitive paths, injection
- Quote shell variables - Always use
"$VAR"not$VAR - Set timeouts - Prevent hanging hooks (default: 60s command, 30s prompt)
- Use absolute paths - Via
$CLAUDE_PROJECT_DIRor${CLAUDE_PLUGIN_ROOT} - Handle errors gracefully - Use
set -euo pipefailin bash - Don't log sensitive data - Filter credentials, tokens, API keys
See references/security.md for detailed security patterns.
Debugging
# Run Claude with debug output
claude --debug
# Test hook manually
echo '{"tool_name": "Write", "tool_input": {"file_path": "test.ts"}}' | ./.claude/hooks/my-hook.sh
# Check transcript for hook execution
# Press Ctrl+R in Claude Code to view transcript
Common issues:
- Hook not firing: Check matcher syntax, restart Claude Code
- Permission errors:
chmod +x script.sh - Timeout: Increase timeout value or optimize script
Workflow Patterns
Pre-Commit Quality Gate
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{"type": "command", "command": "./.claude/hooks/validate-paths.sh"},
{"type": "command", "command": "./.claude/hooks/check-sensitive.sh"}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit(*.ts)",
"hooks": [
{"type": "command", "command": "biome check --write \"$file\""},
{"type": "command", "command": "tsc --noEmit \"$file\""}
]
}
]
}
}
Context Injection
{
"hooks": {
"SessionStart": [{
"matcher": "startup",
"hooks": [{
"type": "command",
"command": "echo \"Branch: $(git branch --show-current)\" && git status --short"
}]
}],
"UserPromptSubmit": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "echo \"Time: $(date '+%Y-%m-%d %H:%M %Z')\""
}]
}]
}
}
References
- references/hook-types.md - Detailed documentation for each hook event
- references/matchers.md - Advanced matcher patterns and MCP tools
- references/security.md - Security best practices and validation patterns
- references/schema.md - Complete configuration schema reference
- references/examples.md - Real-world hook implementations
External Resources
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
stack-feedback
Creates GitHub issues for problems discovered while using @outfitter/* packages. Use when finding bugs, missing features, unclear documentation, or improvement opportunities.
stack-architecture
Design stack-based systems using @outfitter/* packages. Use when planning new projects, choosing packages, designing handler architecture, or when "architecture", "design", "structure", "plan handlers", or "error taxonomy" are mentioned.
stack-templates
Templates for creating handlers, CLI commands, MCP tools, and daemon services following Outfitter Stack conventions. Use when scaffolding new components, creating handlers, adding commands, or when "create handler", "new command", "add tool", "scaffold", "template", or "daemon service" are mentioned.
stack-audit
Scan codebase for Outfitter Stack adoption candidates. Identifies throw statements, console usage, hardcoded paths, and custom errors. Use when assessing adoption scope or checking readiness.
stack-review
Audits code for Outfitter Stack compliance including Result types, error handling, logging patterns, and path safety. Use for pre-commit reviews, code quality checks, migration validation, or when "audit", "check compliance", "review stack", or "stack patterns" are mentioned.
stack-patterns
Reference for Outfitter Stack patterns including Result types, Handler contract, Error taxonomy, and @outfitter/* package conventions. Use when learning the stack, looking up patterns, understanding packages, or when "Result", "Handler", "error taxonomy", "OutfitterError", "CLI output", "pagination", "MCP server", "MCP tool", "structured logging", "redaction", "test handler", "daemon", "IPC", or "@outfitter/*" are mentioned.
Didn't find tool you were looking for?