Agent Skills: Claude Code Hooks Configuration

|

UncategorizedID: laurigates/claude-plugins/claude-hooks-configuration

Install this agent skill to your local

pnpm dlx add-skill https://github.com/laurigates/claude-plugins/tree/HEAD/agent-patterns-plugin/skills/claude-hooks-configuration

Skill Files

Browse the full folder contents for claude-hooks-configuration.

Download Skill

Loading file tree…

agent-patterns-plugin/skills/claude-hooks-configuration/SKILL.md

Skill Metadata

Name
claude-hooks-configuration
Description
|

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):

  1. Background subshell - Run slow operations in background, exit immediately
  2. 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.tomlcommand_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

  1. Use background subshell - Wrap slow operations in ( ) &>/dev/null & and exit 0
  2. Set explicit timeouts - Add timeout field for hooks requiring synchronous execution
  3. Test hook timing - Use time bash ~/.claude/script.sh to measure execution
  4. Redirect all output - Use &>/dev/null to prevent blocking on stdout/stderr
  5. Apply /hooks menu - Use Claude Code's hook menu to reload settings after changes