You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Complete reference for Claude Code hook events, configuration, and debugging.
Overview
Hooks are shell scripts that execute at specific points in the Claude Code lifecycle. They are configured in settings.json under the hooks key and are installed automatically by setup.sh.
Hooks can validate input, block operations, inject context, log activity, and trigger side effects.
Hook Events (27 total)
Session Lifecycle
Event
When
Matcher Values
Blocking
SessionStart
Session begins (new, resume, compact, clear)
startup, resume, clear, compact
Yes
SessionEnd
Session is ending
--
No (always async)
Setup
Triggered via --init, --init-only, or --maintenance CLI flags
--
Yes
User Interaction
Event
When
Matcher Values
Blocking
UserPromptSubmit
User submits a prompt, before Claude sees it
--
Yes
Notification
Claude sends a notification to the user
Notification type
No
Stop
Claude finishes generating a response
--
Yes
StopFailure
Turn ends due to API error (rate limit, auth failure)
MCP server requests structured user input mid-task
--
Yes
ElicitationResult
User responds to MCP elicitation dialog
--
No
Worktree Events
Event
When
Matcher Values
Blocking
WorktreeCreate
Worktree being created (can replace default git behavior)
--
Yes
WorktreeRemove
Worktree being removed
--
No
Environment Variables
Each hook event has access to different environment variables.
Universal Variables
Available in all hooks:
Variable
Description
$HOME
User home directory
$PWD
Current working directory
Event-Specific Variables
Event
Variable
Description
UserPromptSubmit
$PROMPT
The user's prompt text
PreToolUse
$TOOL_NAME
Name of the tool about to execute
PreToolUse
$TOOL_INPUT
JSON string of tool input parameters
PostToolUse
$TOOL_NAME
Name of the tool that executed
PostToolUse
stdin (JSON)
Full hook context: tool_input, tool_response, tool_name, session_id, cwd
PostToolUseFailure
$TOOL_NAME
Name of the tool that failed
PostToolUseFailure
$TOOL_ERROR
Error message from the failure
SubagentStart
$AGENT_ID
Unique identifier for the subagent
SubagentStart
$AGENT_TYPE
Agent type (e.g., explore, planner)
SubagentStop
$AGENT_ID
Unique identifier for the subagent
SubagentStop
$AGENT_TYPE
Agent type (e.g., explore, planner)
Notification
$NOTIFICATION_MESSAGE
The notification text
PermissionDenied
$TOOL_NAME
Name of the tool that was denied
PermissionDenied
$PERMISSION_DECISION_REASON
Reason for the denial
Flattened Tool Input Variables
For PreToolUse hooks, Claude Code also provides flattened versions of the $TOOL_INPUT JSON object as individual environment variables. Note: these are NOT available in PostToolUse hooks — PostToolUse receives data on stdin instead. Each top-level key in the tool input becomes TOOL_INPUT_<key>.
This is more convenient than parsing the raw $TOOL_INPUT JSON string in shell scripts.
Variable
Available In
Description
TOOL_INPUT_command
PreToolUse(Bash), PostToolUse(Bash)
The shell command being executed
TOOL_INPUT_file_path
PreToolUse(Edit), PostToolUse(Write|Edit)
The file path being written or edited
Example usage in a hook script:
#!/usr/bin/env bash# Access the file path directly instead of parsing $TOOL_INPUT JSON
FILE_PATH="${TOOL_INPUT_file_path:-}"
[[ -z"$FILE_PATH" ]] &&exit 0
# Now use $FILE_PATH directlyecho"Edited file: $FILE_PATH"
The flattened variables follow the naming convention TOOL_INPUT_<key> where <key> matches the parameter name from the tool's input schema (e.g., file_path, command, old_string, new_string, pattern).
Filter which tools/events trigger the hook (see Matcher Patterns below)
if
string
--
Conditional filter using permission rule syntax (e.g., "Bash(git commit*)", "Bash(bun add*) Bash(npm install*)") — more precise than matcher for command-level filtering
hooks
list
(required)
Array of hook actions to execute
if vs matcher: Use matcher to filter by tool name ("Bash", "Edit", "Write|Edit"). Use if to filter by specific command patterns within a tool, avoiding the need for shell-script grep matching. Space-separated patterns are OR'd together.
Hook Action Fields
Field
Type
Default
Description
type
string
(required)
"command" (shell), "prompt" (LLM yes/no), "agent" (subagent with tools), or "http" (webhook)
command
string
(required for command type)
Shell command to execute
async
boolean
false
Run in background without blocking Claude
timeout
number
600
Timeout in seconds (max: 600)
once
boolean
false
Run exactly once per session, then disable
Hook Types
Type
Description
Use For
command
Executes a shell command. Exit code 0 = pass, non-zero = block (for PreToolUse). stdout is injected into Claude's context.
Validation, logging, context injection
prompt
Sends a single-turn prompt to Claude for yes/no evaluation.
Complex validation requiring LLM judgment
agent
Spawns a subagent with tool access (Read, Grep, Glob) for verification.
Multi-step validation requiring code inspection
http
Sends a webhook to a URL. Configure with url, headers, allowedEnvVars. Controlled by allowedHttpHookUrls setting.
Plays audio notification on git commit (when audio enabled)
Yes
PostToolUse (Bash matcher — command logging)
Script
Purpose
Async
log-bash.sh
Logs every Bash command to ~/.claude/logs/bash-YYYY-MM-DD.log
Yes
Logs are used by the /audit skill (claude-audit.sh) to analyze command patterns, security concerns, and repeated commands. Hook receives JSON on stdin with tool_input.command.
Log format:[HH:MM:SS] [project] command
Retention: Controlled by CLAUDE_LOG_RETENTION_DAYS env var (default: 1 day, today only). Old logs are pruned automatically on each hook fire.
PostToolUse (mcp__tldr matcher)
Script
Purpose
Async
track-tldr.sh "$TOOL_NAME"
Tracks TLDR MCP usage statistics
Yes
PostToolUseFailure
Script
Purpose
Async
post-failure.sh
Logs tool failures, warns if same tool fails 3+ times in a session
No
notify-sound.sh error
Plays audio notification on tool failure (when audio enabled)
Yes
PreCompact
Script
Purpose
Async
handoff.sh create
Saves current task state to handoff file before context is compacted
No
notify-sound.sh compact
Plays audio notification before compaction (when audio enabled)
Yes
Stop
Script
Purpose
Async
Inline learning reminder
If >5 files were changed, reminds to store learnings
No
compact-reminder.sh
Suggests /compact after context-heavy skill operations
No
notify-sound.sh task_complete
Plays audio notification on task completion (when audio enabled)
Yes
SubagentStart / SubagentStop
Script
Purpose
Async
Log to ~/.claude/swarm.log
Records agent spawn/stop events with agent type and ID
Yes
Notification
Script
Purpose
Async
notify.sh
Sends desktop notification (macOS/Linux) when tasks complete
Yes
SessionEnd
Script
Purpose
Async
tldr-stats.sh + handoff.sh create
Prints TLDR session stats and saves final handoff state
Logs denial to swarm.log, handles denied permissions, plays alert sound
Yes
Adding New Hooks
Step 1: Create the Script
#!/bin/bash# ~/.claude/scripts/my-hook.sh# Description of what this hook does# Access environment variablesecho"[Hook] Processing: $TOOL_NAME"# Exit 0 to allow, non-zero to block (PreToolUse only)exit 0
Run a Claude Code session and trigger the relevant event. Check logs for output.
Best Practices
Always quote $HOME paths in the command string.
Use async: true for non-blocking operations (logging, metrics).
Set reasonable timeout values (default 600s is often too long for simple scripts).
For PreToolUse hooks: exit 0 means allow, exit 2 means block (output JSON with {"decision":"block","reason":"..."}). Include a clear message explaining why.
Write to log files for debugging rather than relying on stdout for async hooks.
Debugging Hooks
Log Locations
Log File
Contents
~/.claude/hooks.log
General hook execution output
~/.claude/swarm.log
Subagent start/stop events
~/.claude/sessions.log
Session lifecycle events
~/.claude/skill-activation.out
Skill pattern matching results
~/.claude/tldr-session-stats.json
TLDR tool usage statistics per session
~/.claude/logs/tool-failures.log
Tool failure events (from post-failure.sh)
~/.claude/safety-net.log
Blocked command audit log (from safety-net.sh)
~/.claude/logs/bash-*.log
Daily Bash command logs (from log-bash.sh, analyzed by /audit)
Common Issues
Symptom
Likely Cause
Fix
Hook never fires
Wrong matcher value
Check tool name spelling; use Write|Edit not Write, Edit
Hook fires but no effect
Script not executable
Run chmod +x on the script
Hook blocks unexpectedly
Script exits non-zero
Add error handling; check exit codes
Hook output not visible
Hook is async
Async hooks don't inject into context; switch to sync if needed
Hook causes timeout
Script hangs
Add timeout to the script itself; reduce timeout value
$TOOL_INPUT is empty
Wrong event
TOOL_INPUT is only available in PreToolUse and PostToolUse
Path not found
Missing quotes
Always use bash "$HOME/.claude/scripts/..." with quotes