Hooks & Lifecycle
Hooks run commands on Claude Code events, letting plugins inject context, validate changes, and react to tool use. Each event in the session lifecycle can trigger zero or more hooks.
Session Lifecycle
Hook Events
SessionStart
Fires once when a new Claude Code session opens. Typical use: inject ambient context (project structure, conventions, current task status).
Timeout: 2-5 seconds typical. Default 60 seconds.
Example: The steering plugin injects .agents/steering/ file structure at start so Claude knows to check for context.md files.
UserPromptSubmit
Fires every time the user submits a message. Typical use: add nudges, reminders, or status updates to the model context for every user input.
Timeout: 2-5 seconds typical.
Example: Echo a reminder that system-prompts should be consulted.
PreToolUse
Fires before a tool is invoked (e.g., Read, Write, Bash). Typical use: validate tool use, inject context about the specific tool, or prevent certain operations.
Timeout: 2-5 seconds typical.
Example: Inject steering context before a Task tool is used.
PostToolUse
Fires after a tool completes. Typical use: react to results, trigger cleanup, or record tool use.
Timeout: 2-5 seconds typical.
Example: Run linting after a Write tool creates a file.
Stop
Fires when Claude Code session ends. Typical use: cleanup, final reporting, or state persistence.
Timeout: 5-10 seconds typical.
Hook Configuration
Hooks live in plugins/<name>/hooks/hooks.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/lint.sh",
"timeout": 5
}
]
}
]
}
}Matchers
Scope hooks to specific tools:
{
"matcher": "Write|Edit|Bash"
}Omit matcher to run on all tool invocations of that event type.
Paths
Use ${CLAUDE_PLUGIN_ROOT} to reference plugin files:
{
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/validate.sh"
}This expands to the plugin's root directory at runtime.
Timeouts
- SessionStart, UserPromptSubmit, PreToolUse: 2-5 seconds
- PostToolUse, Stop: 5-10 seconds
If a hook exceeds its timeout, Claude Code skips it.
SessionStart Context Injection
SessionStart hooks can inject context in two ways: plain text output or JSON additionalContext.
Plain Text Output
#!/bin/bash
# Inject context as plain text
cat << EOF
VERY IMPORTANT: Always check .agents/steering/ for context before answering.
.agents/steering/
├── documentation/
├── patterns/
└── overview/
EOF
exit 0Plain text stdout is injected as part of the session prompt.
JSON additionalContext
#!/bin/bash
# Inject context via JSON structure
echo '{"additionalContext": "Project structure: .agents/steering/"}'
exit 0Both methods work. Use whichever fits your hook's output format.
Common Hook Patterns
These patterns show up across plugin hooks often enough to be worth naming.
Conditional Execution
Run only when a directory or file is present:
#!/bin/bash
if [[ -d ".agents/steering" ]]; then
cat << EOF
Context here...
EOF
fi
exit 0External Command Dependency
If a hook depends on a tool that may not be installed, fall back gracefully:
#!/bin/bash
if command -v tree &> /dev/null; then
tree .agents/steering
else
find .agents/steering -type f -name "*.md"
fi
exit 0Timeout-Safe Operations
Cap long operations and exit cleanly even if the inner command times out:
#!/bin/bash
timeout 4s find . -name "*.md" | head -20
exit 0User-Level Hooks
In addition to plugin hooks, users can add hooks to their own Claude Code settings:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "echo 'You just created a file'"
}
]
}
]
}
}See .claude/settings.json in your project or home directory. User hooks run after plugin hooks.
Best Practices
Hooks should finish in 2–5 seconds because they block the session lifecycle. Use matchers to scope hooks to the tools that actually matter — running on every tool slows sessions unnecessarily. For SessionStart context injection, plain text and JSON work equally well; pick the format that fits your hook's output. Failed hooks don't break Claude Code — their output is lost, so write hooks that exit cleanly even when they encounter errors.
Related
- Component Model — Commands, skills, agents
- Plugins Overview — Installation and marketplace