Use when adding new error messages to React, or seeing "unknown error code" warnings.
npx skills add AskTinNguyen/vesper-team-skills --skill "claude-code-hooks"
Install specific skill from multi-skill repository
# Description
This skill should be used when implementing pre-hooks and post-hooks for Claude Code. It applies when users want to add automation, validation, formatting, logging, or custom behavior that triggers before or after Claude's tool executions. Triggers on requests like "add a hook", "run prettier after edits", "validate commands", "log bash commands", "block dangerous operations", or mentions of PreToolUse, PostToolUse, Stop, UserPromptSubmit hooks.
# SKILL.md
name: claude-code-hooks
description: This skill should be used when implementing pre-hooks and post-hooks for Claude Code. It applies when users want to add automation, validation, formatting, logging, or custom behavior that triggers before or after Claude's tool executions. Triggers on requests like "add a hook", "run prettier after edits", "validate commands", "log bash commands", "block dangerous operations", or mentions of PreToolUse, PostToolUse, Stop, UserPromptSubmit hooks.
Claude Code Hooks
Overview
Claude Code hooks are user-defined shell commands that execute automatically at various points in Claude's lifecycle. They provide deterministic control over behavior—ensuring certain actions always happen rather than relying on the LLM to decide.
When to Use Hooks
| Use Case | Hook Event | Example |
|---|---|---|
| Auto-format code after edits | PostToolUse | Run prettier on saved files |
| Validate commands before execution | PreToolUse | Block dangerous rm -rf commands |
| Log all bash commands | PreToolUse | Audit trail for compliance |
| Add context to prompts | UserPromptSubmit | Inject project-specific info |
| Ensure task completion | Stop | Verify all tests pass before stopping |
| Block edits to sensitive files | PreToolUse | Protect .env, credentials |
| Run linters after writes | PostToolUse | ESLint, Rubocop, etc. |
Hook Events Reference
| Event | When It Runs | Can Block? | Common Uses |
|---|---|---|---|
| PreToolUse | Before tool calls | Yes | Validation, security, logging |
| PostToolUse | After tool calls | Limited | Formatting, linting, notifications |
| UserPromptSubmit | When user submits prompt | Yes | Add context, validate input |
| Stop | When Claude finishes | Yes | Force continuation if incomplete |
| SubagentStop | When subagent completes | Yes | Verify subagent work |
| PermissionRequest | Permission dialog shown | Yes | Auto-allow/deny patterns |
| Notification | Claude sends notification | No | Custom notification handling |
| SessionStart | Session begins | No | Environment setup |
| SessionEnd | Session ends | No | Cleanup, logging |
| Setup | With --init flags | Limited | Initial configuration |
| PreCompact | Before context compaction | No | Save important context |
Configuration
Hooks are configured in JSON settings files (in order of precedence):
1. .claude/settings.local.json - Local project (gitignored)
2. .claude/settings.json - Project settings
3. ~/.claude/settings.json - User settings (global)
Basic Structure
{
"hooks": {
"EventName": [
{
"matcher": "ToolPattern",
"hooks": [
{
"type": "command",
"command": "your-bash-command",
"timeout": 60,
"async": false
}
]
}
]
}
}
Hook Options
| Option | Type | Default | Description |
|---|---|---|---|
type |
string | required | "command" (shell) or "prompt" (LLM-based) |
command |
string | required | Shell command to execute (for type: "command") |
prompt |
string | required | LLM prompt (for type: "prompt") |
timeout |
number | 60 | Maximum execution time in seconds |
async |
boolean | false | Run asynchronously without blocking Claude |
Async Hooks
When async: true, the hook runs in the background without blocking Claude's execution. This is ideal for:
- Logging and analytics - Write to files or send telemetry
- Archival - Save state before session ends
- Notifications - Send Slack/Discord webhooks
- Long-running tasks - Operations that don't need to complete before Claude continues
Important: Async hooks cannot:
- Block or modify Claude's actions (exit code 2 is ignored)
- Return output to Claude (stdout/stderr is discarded)
- Guarantee completion before the session ends
{
"hooks": {
"Stop": [{
"hooks": [{
"type": "command",
"command": "~/.claude/scripts/archive-session.sh",
"async": true,
"timeout": 30
}]
}]
}
}
Matcher Patterns
*or""- Match all toolsBash- Match only Bash toolEdit|Write- Match Edit OR Write toolsmcp__server__.*- Match MCP tools (regex)
Note: matcher is not used for UserPromptSubmit, Stop, SubagentStop, Setup, SessionStart, SessionEnd.
Quick Start Examples
Auto-Format with Prettier
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "npx prettier --write \"$CLAUDE_PROJECT_DIR\"",
"timeout": 30
}
]
}
]
}
}
Log All Bash Commands
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.command' >> ~/.claude/bash-log.txt"
}
]
}
]
}
}
Block Dangerous Commands
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 ~/.claude/skills/claude-code-hooks/scripts/block_dangerous.py"
}
]
}
]
}
}
Protect Sensitive Files
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "python3 ~/.claude/skills/claude-code-hooks/scripts/protect_files.py"
}
]
}
]
}
}
Archive Tasks on Session End (Async)
{
"hooks": {
"Stop": [{
"hooks": [{
"type": "command",
"command": "~/.claude/skills/dispatch/hooks/auto-archive.sh",
"async": true,
"timeout": 30
}]
}]
}
}
This archives task lists to ~/.claude/tasks-archive/ when sessions end, running asynchronously so it doesn't delay Claude's response.
Send Slack Notification (Async)
{
"hooks": {
"Stop": [{
"hooks": [{
"type": "command",
"command": "curl -X POST -d '{\"text\":\"Claude session complete\"}' $SLACK_WEBHOOK_URL",
"async": true,
"timeout": 10
}]
}]
}
}
Hook Input/Output
Input (via stdin)
Hooks receive JSON with context:
{
"session_id": "abc123",
"cwd": "/path/to/project",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm run build",
"description": "Build the project"
}
}
Output (via exit code + stdout/stderr)
| Exit Code | Meaning | Behavior |
|---|---|---|
| 0 | Success | Continue, parse JSON stdout for control |
| 2 | Block | Stop action, show stderr to Claude |
| Other | Error | Log stderr, continue execution |
Control via JSON Output
For advanced control, output JSON with exit code 0:
{
"continue": true,
"suppressOutput": false,
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"additionalContext": "Extra info for Claude"
}
}
Writing Hook Scripts
Template for PreToolUse Validation
#!/usr/bin/env python3
import json
import sys
def main():
try:
data = json.load(sys.stdin)
tool_input = data.get('tool_input', {})
# Your validation logic here
if should_block(tool_input):
print("Blocked: reason here", file=sys.stderr)
sys.exit(2) # Block the action
# Optionally add context
print(json.dumps({
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"additionalContext": "Hook approved this action"
}
}))
sys.exit(0)
except Exception as e:
print(f"Hook error: {e}", file=sys.stderr)
sys.exit(1)
def should_block(tool_input):
# Implement your logic
return False
if __name__ == "__main__":
main()
Template for PostToolUse Actions
#!/usr/bin/env python3
import json
import sys
import subprocess
def main():
try:
data = json.load(sys.stdin)
tool_name = data.get('tool_name', '')
tool_input = data.get('tool_input', {})
# Example: Run formatter on edited files
if tool_name in ['Edit', 'Write']:
file_path = tool_input.get('file_path', '')
if file_path.endswith(('.ts', '.tsx', '.js', '.jsx')):
subprocess.run(['npx', 'prettier', '--write', file_path])
sys.exit(0)
except Exception as e:
print(f"Hook error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
Environment Variables
Available in hook scripts:
| Variable | Description |
|---|---|
$CLAUDE_PROJECT_DIR |
Absolute path to project root |
$CLAUDE_CODE_REMOTE |
"true" for web, empty for CLI |
$CLAUDE_ENV_FILE |
File to write env vars (SessionStart only) |
Debugging Hooks
- Use debug mode:
claude --debug - Check verbose output: Press
ctrl+oin Claude Code - Test hooks manually: Pipe test JSON to your script
echo '{"tool_input":{"command":"rm -rf /"}}' | python3 my_hook.py
echo $? # Check exit code
- Use the
/hookscommand in Claude Code to review active hooks
Best Practices
- Always quote variables: Use
"$VAR"not$VAR - Use absolute paths: Reference scripts with full paths or
$CLAUDE_PROJECT_DIR - Set reasonable timeouts: Default is 60s, adjust as needed
- Handle errors gracefully: Exit 1 for non-blocking errors, 2 to block
- Test thoroughly: Hooks run automatically—bugs can be disruptive
- Keep hooks fast: They run synchronously and affect responsiveness
- Use
async: truefor non-blocking tasks: Logging, archival, notifications, and analytics that don't need to block Claude
Security Considerations
- Hooks execute with your credentials
- Always review hook code before adding
- Validate and sanitize all inputs
- Block path traversal (
..in paths) - Protect sensitive files (.env, .git/, keys)
Resources
This skill includes helper scripts in the scripts/ directory:
| Script | Hook Event | Purpose |
|---|---|---|
block_dangerous.py |
PreToolUse | Block dangerous bash commands (rm -rf, etc.) |
protect_files.py |
PreToolUse | Protect sensitive files (.env, credentials) |
format_on_save.py |
PostToolUse | Auto-format files after edits |
log_commands.py |
PreToolUse | Log all bash commands to audit file |
save_quality_prompts.py |
UserPromptSubmit | Auto-save high-quality prompts (regex) |
save_quality_prompts_llm.py |
UserPromptSubmit | Auto-save high-quality prompts (LLM) |
save_implementation_plans.py |
PostToolUse | Save approved plans to separate repo |
compound_docs_trigger.py |
UserPromptSubmit | Remind about compound-docs after fixes |
Save Quality Prompts
Automatically saves your high-quality prompts to ~/.claude/saved-prompts.md:
{
"hooks": {
"UserPromptSubmit": [{
"hooks": [{
"type": "command",
"command": "python3 ~/.claude/skills/claude-code-hooks/scripts/save_quality_prompts.py"
}]
}]
}
}
LLM-enhanced version (uses local Qwen model, runs in background):
"command": "python3 ~/.claude/skills/claude-code-hooks/scripts/save_quality_prompts_llm.py"
Save Implementation Plans
Automatically saves approved Claude Code plans to a separate repository:
{
"hooks": {
"PostToolUse": [{
"matcher": "Write|Edit",
"hooks": [{
"type": "command",
"command": "python3 ~/.claude/skills/claude-code-hooks/scripts/save_implementation_plans.py"
}]
}]
}
}
Configuration:
# Set custom repo location (default: ~/.claude/saved-plans/)
export CLAUDE_PLANS_REPO=~/repos/my-plans
# Enable git auto-commit
export CLAUDE_PLANS_GIT_COMMIT=true
# Enable git auto-push (requires CLAUDE_PLANS_GIT_COMMIT)
export CLAUDE_PLANS_GIT_PUSH=true
Repository structure:
~/.claude/saved-plans/
├── index.md # Auto-generated index
└── 2026/01/
├── 2026-01-21-project-auth-refactor.md
└── 2026-01-21-api-rate-limiting.md
See references/hook_events.md for detailed documentation on each hook event and its specific input/output format.
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.