Hooks and Lifecycle Events: Intercepting Agent Behavior¶
Hooks run deterministic code at defined points in an agent's execution — around tool calls and session boundaries — enforcing what instructions cannot guarantee.
Also known as
How Hooks Work · Hook Lifecycle · Hook Lifecycle Events. For practical enforcement patterns using hooks, see Hook Catalog: Guardrails, Sandboxing, and CLI Enforcement.
Canonical home for hooks content
Canonical entry point for "hooks" content site-wide — hook pages elsewhere link here for the lifecycle model rather than restate it (see Related).
The Role of Hooks¶
Instructions are processed by the model; hooks are processed by the shell. Under task pressure the model may deprioritize an instruction — a hook executes unconditionally. The runtime spawns the hook as a PreToolUse or PostToolUse subprocess before (or after) the model acts, then proceeds or blocks based on its exit code and JSON response. The model cannot override hook logic through reasoning.
Lifecycle Events¶
Agent runtimes expose hooks at these points:
| Event | When it fires | Can block |
|---|---|---|
PreToolUse |
Before a tool call executes | Yes |
PostToolUse |
After a tool call completes | No |
UserPromptSubmit |
User sends a message | Yes (some tools) |
Stop |
Agent finishes a turn | No |
InstructionsLoaded |
CLAUDE.md or .claude/rules/*.md loads |
No |
SubagentStart / SubagentStop |
Sub-agent spawns or completes | No |
PreToolUse enforces — it receives the tool name and inputs, can block, and returns a reason the model must adapt to. PostToolUse automates — observational, but triggers side effects like logging or linting.
See Claude Code hooks and Copilot hooks.
Hook Input and Output¶
Hooks receive event-context JSON on stdin:
{
"tool_name": "Bash",
"tool_input": {
"command": "npm install lodash"
}
}
PreToolUse blocks by returning:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Use bun instead of npm"
}
}
The permissionDecisionReason is surfaced to the model — write it as a redirect.
Scoping¶
Matchers scope hooks to specific tools:
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{"type": "command", "command": ".claude/hooks/enforce-bun.sh"}]
}]
}
}
Un-matched hooks fire on every event of that type. Session-level events (Stop, UserPromptSubmit) don't support matchers.
Hook Placement by Use Case¶
| Use case | Event | Approach |
|---|---|---|
| Block disallowed commands | PreToolUse (Bash) |
Deny + redirect |
| Enforce package manager | PreToolUse (Bash) |
Match npm, redirect to bun |
| Auto-lint after edits | PostToolUse (Edit/Write) |
Run linter |
| Audit tool calls | PostToolUse |
Append to log |
| Notify on completion | Stop |
Desktop notification |
| Scan for secrets | PreToolUse (Write) |
Match content, deny if found |
Agent Identity and Worktree Context¶
In multi-agent workflows (Claude Code v2.1.69), hook events carry extra context:
agent_id/agent_type— distinguish main agent, sub-agents, and team members for per-agent enforcement or audit tagging.worktree— status hooks receive worktree name, path, and branch.
InstructionsLoaded fires when CLAUDE.md or .claude/rules/*.md load — log it to audit which files were in effect at session start.
What Hooks Are Not For¶
Hooks enforce what the model should not decide. Avoid them for:
- Creative choices — architecture or style; model judgment is the point.
- Conversation-dependent logic — hooks see only the immediate tool call.
- Complex workflows — hooks are scripts; keep them short and deterministic.
Configuration Levels¶
Configure at project level (.claude/settings.json, committed) or user level (~/.claude/settings.json, local). Project hooks travel with the repo and enforce team conventions.
Key Takeaways¶
- Hooks execute in the shell — the model cannot override them
PreToolUseenforces;PostToolUseautomates- Matchers scope hooks to specific tools; session events fire unconditionally
- Project hooks in
.claude/settings.jsonenforce team conventions InstructionsLoadedaudits which instruction files loaded at session startagent_id/agent_typeenable per-agent enforcement in multi-agent workflows
Example¶
Enforce bun over npm for every Bash call. The hook reads tool input from stdin, matches npm commands, and blocks with a redirect.
.claude/hooks/enforce-bun.sh:
#!/usr/bin/env bash
set -euo pipefail
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if echo "$COMMAND" | grep -qE '\bnpm\s+(install|ci|run|exec|init)\b'; then
cat <<'EOF'
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Use bun instead of npm. Replace npm install with bun install, npm run with bun run."
}
}
EOF
fi
.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/enforce-bun.sh"
}
]
}
]
}
}
When the agent runs npm install lodash, the hook denies the call and surfaces the redirect. The agent retries with bun install lodash.
When This Backfires¶
Hooks are subprocesses — each matched call pays the startup cost, so a slow PreToolUse script delays every matched invocation.
- The model routes around blocks: hooks enforce at the tool-call boundary, not the intent boundary. Block
Writeand the model uses aBashheredoc; blockrmand it deletes viaperl -e 'unlink(...)'. Denies need coverage of every tool that can achieve the same effect (Boucle, What Claude Code Hooks Can and Cannot Enforce, 2026). - Enforcement doesn't follow into subagents, MCP, or pipe mode: a hook that works in the parent session can silently fail in sub-agents, MCP server calls, or pipe mode. For boundaries that must hold everywhere, use OS-level controls — file permissions, network policy, containers.
- Pattern match misses: matchers that don't cover all variants (
npmvsnpxvsnpm.cmd) let violations through silently. - Errors suppress feedback: a hook that exits non-zero without valid JSON may have its error swallowed.
- Re-entrancy loops: a
PostToolUsehook that invokes a tool can retrigger itself. - Non-deterministic input rewrites: multiple
PreToolUsehooks returningupdatedInputfor the same tool race — last to finish wins. - Scope creep: un-scoped hooks fire on every event of that type, taxing all tool calls.
Related¶
- Hook Catalog: Guardrails, Sandboxing, and CLI Enforcement
- Conditional Hook Execution
- StopFailure Hook: Observability for API Error Termination
- On-Demand Skill Hooks: Session-Scoped Guardrails via Skill Invocation
- PreCompact Hook: Vetoing Compaction at Lifecycle Boundaries
- PostToolUse Hooks: Automatic Formatting and Linting After Every File Edit
- Hooks for Enforcement vs Prompts for Guidance
- Enforcing Agent Behavior with Hooks — block, rewrite, and completion-gate patterns built on these lifecycle events