with one click
claude-hooks-configuration
// Set up Claude Code lifecycle hooks in settings.json. Use when triggering a script on session start, running PreToolUse/PostToolUse hooks, configuring timeouts, or debugging hooks.
// Set up Claude Code lifecycle hooks in settings.json. Use when triggering a script on session start, running PreToolUse/PostToolUse hooks, configuring timeouts, or debugging hooks.
[HINT] Download the complete skill directory including SKILL.md and all related files
| name | claude-hooks-configuration |
| description | Set up Claude Code lifecycle hooks in settings.json. Use when triggering a script on session start, running PreToolUse/PostToolUse hooks, configuring timeouts, or debugging hooks. |
| user-invocable | false |
| allowed-tools | Bash(cat *), Bash(bash *), Read, Write, Edit, Grep, Glob, TodoWrite |
| created | "2025-12-27T00:00:00.000Z" |
| modified | "2026-05-09T00:00:00.000Z" |
| reviewed | "2026-04-25T00:00:00.000Z" |
| Use this skill when... | Use plugin-settings instead when... |
|---|---|
Wiring a SessionStart, PreToolUse, PostToolUse, or Stop hook into .claude/settings.json | Storing per-project plugin state in .claude/plugin-name.local.md |
| Setting explicit hook timeouts to prevent "Hook cancelled" errors | Configuring user-tunable plugin behaviour exposed by skill authors |
Choosing between command, http, prompt, and agent hook types | Persisting agent state between sessions outside the hook lifecycle |
| Debugging a hook that fires inconsistently or is silently skipped | Configuring per-project rather than per-session behaviour |
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 | 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.
| 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: true on command hooks: fire-and-forget, does not block the operationonce: true on any hook handler: runs only once per session, subsequent triggers skippedFor full event reference, schemas, and examples, see .claude/rules/hooks-reference.md.
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):
timeout field to hook configurationHooks are configured in .claude/settings.json:
~/.claude/settings.json<project>/.claude/settings.json{
"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
}
]
}
]
}
}
| 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 |
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 blockingexit 0 returns success immediatelyComparison of approaches:
| Approach | Portability | Speed | Notes |
|---|---|---|---|
( ) & | bash, zsh, sh | Instant | Recommended |
disown | Bash-only | Instant | Not POSIX |
nohup | POSIX | Slight overhead | Overkill for hooks |
If you need synchronous execution, add explicit timeout to settings:
cat ~/.claude/settings.json | jq '.hooks'
# Edit to add "timeout": <seconds> to each hook
| Optimization | Pattern |
|---|---|
| Background subshell | ( commands ) &>/dev/null & |
| Fast test modes | --bail=1, -x, --dots |
| Skip heavy operations | Conditional execution |
| Parallel execution | Use & and wait |
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
| 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 |
| 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 | 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 |
( ) &>/dev/null & and exit 0timeout field for hooks requiring synchronous executiontime bash ~/.claude/script.sh to measure execution&>/dev/null to prevent blocking on stdout/stderr/hooks menu - Use Claude Code's hook menu to reload settings after changes