Claude Code Hooks Configuration
Core Expertise
Configure Claude Code lifecycle hooks (all 17 events including SessionStart, Stop, PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, WorktreeCreate, TeammateIdle, TaskCompleted, ConfigChange, and more) with proper timeout settings to prevent "Hook cancelled" errors during session management.
Hook Events
Hook Trigger Default Timeout
SessionStart
When Claude Code session begins 600s (command)
SessionEnd
When session ends or /clear runs 600s (command)
Stop
When main agent stops responding 600s (command)
SubagentStop
When a subagent (Task tool) finishes 600s (command)
PreToolUse
Before a tool executes 600s (command)
PostToolUse
After a tool completes 600s (command)
PostToolUseFailure
After a tool execution fails 600s (command)
PermissionRequest
When Claude requests permission for a tool 600s (command)
WorktreeCreate
New git worktree created via EnterWorktree 600s (command)
WorktreeRemove
Worktree removed after session exits 600s (command)
TeammateIdle
Teammate in agent team goes idle 600s (command)
TaskCompleted
Task in shared task list marked complete 600s (command)
ConfigChange
Claude Code settings change at runtime 600s (command)
Default timeouts: command = 600s, prompt = 30s, agent = 60s. Always set explicit timeouts — it documents intent.
Hook Types
Type How It Works Default Timeout Use When
command
Runs a shell command, reads stdin, returns exit code/JSON 600s Deterministic rules
http
Sends hook data to an HTTPS endpoint 30s Remote/centralized policy
prompt
Single-turn LLM call, returns {ok: true/false}
30s Judgment on hook input data
agent
Multi-turn subagent with tool access, returns {ok: true/false}
60s Verification needing file/tool access
Async and Once
-
async: true on command hooks: fire-and-forget, does not block the operation
-
once: true on any hook handler: runs only once per session, subsequent triggers skipped
For full event reference, schemas, and examples, see .claude/rules/hooks-reference.md.
Common Issue: Hook Cancelled Error
SessionEnd hook [bash ~/.claude/session-logger.sh] failed: Hook cancelled
Root cause: Hook execution exceeds the configured timeout. With the 2.1.50 default of 10 minutes, this is now less common — but explicitly setting "timeout" in your hook config is still recommended.
Solutions (in order of preference):
-
Background subshell - Run slow operations in background, exit immediately
-
Explicit timeout - Add timeout field to hook configuration
Hook Configuration
Location
Hooks are configured in .claude/settings.json :
-
User-level: ~/.claude/settings.json
-
Project-level: <project>/.claude/settings.json
Structure with Timeout
{
"hooks": {
"SessionEnd": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "/.claude/session-logger.sh",
"timeout": 120
}
]
}
],
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "/.claude/session-setup.sh",
"timeout": 180
}
]
}
],
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "~/.claude/stop-hook-git-check.sh",
"timeout": 30
}
]
}
]
}
}
Timeout Guidelines
Hook Type Recommended Timeout Use Case
SessionStart 120–300s Tests, linters, dependency checks
SessionEnd 60–120s Logging, cleanup, state saving
Stop / SubagentStop 30–60s Git status checks, quick validations
PreToolUse 10–30s Quick validations
PostToolUse 30–120s Logging, notifications
PermissionRequest 5–15s Keep fast for good UX
Fixing Timeout Issues
Recommended: Background Subshell Pattern
The most portable and robust solution is to run slow operations in a background subshell and exit immediately:
#!/bin/bash
~/.claude/session-logger.sh
Exits instantly, work continues in background
(
All slow operations go here
echo "$(date): Session ended" >> ~/.claude/session.log curl -s -X POST "https://api.example.com/log" -d "session_end=$(date)"
Any other slow work...
) &>/dev/null &
exit 0
Why this works:
-
( ) creates a subshell for the commands
-
& runs the subshell in background
-
&>/dev/null prevents stdout/stderr from blocking
-
exit 0 returns success immediately
Comparison of approaches:
Approach Portability Speed Notes
( ) &
bash, zsh, sh Instant Recommended
disown
Bash-only Instant Not POSIX
nohup
POSIX Slight overhead Overkill for hooks
Alternative: Increase Timeout
If you need synchronous execution, add explicit timeout to settings:
cat ~/.claude/settings.json | jq '.hooks'
Edit to add "timeout": <seconds> to each hook
Script Optimization Patterns
Optimization Pattern
Background subshell ( commands ) &>/dev/null &
Fast test modes --bail=1 , -x , --dots
Skip heavy operations Conditional execution
Parallel execution Use & and wait
Related: Starship Timeout
If you see:
[WARN] - (starship::utils): Executing command "...node" timed out.
This is a separate starship issue. Fix by adding to ~/.config/starship.toml :
command_timeout = 1000 # 1 second (default is 500ms)
For slow node version detection:
[nodejs] disabled = false detect_files = ["package.json"] # Skip .nvmrc to speed up detection
[command] command_timeout = 2000 # Increase if still timing out
Agentic Optimizations
Context Command
View hooks config cat ~/.claude/settings.json | jq '.hooks'
Test hook script time bash ~/.claude/session-logger.sh
Find slow operations bash -x ~/.claude/session-logger.sh 2>&1 | head -50
Check starship config starship config
Quick Reference
Setting Location Default
Hook timeout .claude/settings.json → hook → timeout
10 minutes (600s) since 2.1.50
Starship timeout ~/.config/starship.toml → command_timeout
500ms
Node detection ~/.config/starship.toml → [nodejs]
Auto
Error Handling
Error Cause Fix
Hook cancelled Timeout exceeded Add explicit "timeout" (e.g. "timeout": 120 )
Hook failed Script error Check exit code, add error handling
Command not found Missing script Verify script path and permissions
Permission denied Script not executable chmod +x ~/.claude/script.sh
Best Practices
-
Use background subshell - Wrap slow operations in ( ) &>/dev/null & and exit 0
-
Set explicit timeouts - Add timeout field for hooks requiring synchronous execution
-
Test hook timing - Use time bash ~/.claude/script.sh to measure execution
-
Redirect all output - Use &>/dev/null to prevent blocking on stdout/stderr
-
Apply /hooks menu - Use Claude Code's hook menu to reload settings after changes