Claude Code Hooks: The Deterministic Control Layer for AI Agents
A CLAUDE.md instruction says “always run the linter.” The agent usually complies. A PostToolUse hook runs the linter after every file write, every single time, no exceptions. That gap between “usually” and “always” is where production systems fail.

Figure 1 - The Reliability Gap: Prompts vs Hooks: Prompt-based instructions achieve 70-90% compliance. The agent usually follows them but can skip under context pressure, long sessions, or competing priorities. Hooks achieve 100% compliance. They execute at the system level, outside the LLM’s reasoning chain. For anything that must happen, hooks are the enforcement mechanism.
You write “always run the linter after writing code” in CLAUDE.md. Your agent follows the instruction. Most of the time. But on session 47 of a long project, after a complex debugging chain that consumed most of the context window, the agent writes a file and moves on. No linter. No type check. The bug ships.
With hooks, the setup works differently. A PostToolUse hook fires after every file write. It runs the linter. If there are errors, it injects the error descriptions back into the agent’s context as additionalContext. The agent fixes the issues in its next action. This happens on every write: session 1, session 47, session 200. The hook does not forget. It does not reason. It does not skip.
That is the difference between probabilistic compliance and deterministic control. Without hooks, you are essentially “vibe coding at scale.” With hooks, you have reproducible, auditable, governable agent behavior.
Claude Code Hooks are user-defined shell commands that execute at specific points in Claude Code’s lifecycle [1]. They are not prompts. They are not suggestions. They are system-level interceptors that guarantee certain actions always happen (safety checks, quality validation, observability logging, and workflow enforcement) regardless of what the agent’s reasoning chain looks like.
This article covers how hooks work, the complete event system, the three hook types, and the architectural patterns that make agent teams safe and reliable in production.
How Hooks Work
At their core, hooks are shell commands that Claude Code executes at predefined points during its operation [1]. You configure them in your project’s .claude/settings.json file, specifying which events trigger which commands. When the event fires, Claude Code pipes a JSON payload containing the event context to your hook script’s stdin, and your script can inspect the payload, perform validation, and optionally return a JSON response that influences Claude’s behavior.
Think of hooks as middleware in a web framework. Just as Express.js middleware intercepts HTTP requests before they reach your route handler, hooks intercept Claude Code’s tool calls, session events, and permission requests before they execute. The difference is that hooks operate at the AI agent level, intercepting decisions rather than network requests.

Figure 2 - Hooks as Agent Middleware: Hooks intercept the agent’s tool calls at 2 critical points. PreToolUse hooks act as safety gates before execution, allowing, denying, or escalating to human confirmation. PostToolUse hooks act as quality gates after execution, running validators and injecting feedback back into the agent’s context. Together, they create a deterministic control layer around every agent action.
Here is a minimal example. This PreToolUse hook blocks any Bash command containing destructive patterns:
#!/usr/bin/env python3"""PreToolUse hook: Block dangerous shell commands."""import jsonimport sys
BLOCKED_PATTERNS = ["rm -rf", "DROP TABLE", "format c:", "shutdown", "> /dev/sda"]
def main(): input_data = json.loads(sys.stdin.read()) tool_name = input_data.get("tool_name", "")
if tool_name != "Bash": sys.exit(0) # Not a bash command, allow it
command = input_data.get("tool_input", {}).get("command", "")
for pattern in BLOCKED_PATTERNS: if pattern.lower() in command.lower(): output = { "decision": "deny", "reason": f"Blocked: command contains '{pattern}'" } print(json.dumps(output)) sys.exit(0)
sys.exit(0) # No blocked pattern found, allow execution
if __name__ == "__main__": main()Without this hook, you would need to write “never run dangerous commands” in your CLAUDE.md and trust that the model always follows the instruction. With this hook, dangerous commands are physically blocked at the execution layer. The agent cannot bypass it, forget it, or reason its way around it.
Hooks guarantee behavior; prompts suggest it. For anything that must happen (safety checks, linting, test runs, file ownership enforcement), hooks are the enforcement mechanism. Prompts guide how the agent approaches tasks. Hooks enforce what the agent cannot violate. Use both, but never rely on prompts alone for non-negotiable constraints.
Prompts vs Hooks: The Reliability Comparison
This distinction is worth emphasizing because it is the core architectural insight behind hooks.

Figure 3 - Four Approaches to Agent Control: From unreliable prompt reminders to deterministic hooks with intelligent evaluation. The key insight is that hooks and prompts serve different purposes: prompts guide the agent’s approach, hooks enforce non-negotiable constraints. The strongest systems use both.
| Approach | Mechanism | Reliability | Auditability |
|---|---|---|---|
| CLAUDE.md instruction | ”Always run the linter after writing code” | Probabilistic: usually complies but may skip | None unless you check manually |
| Hook | PostToolUse hook runs linter after every file write | Deterministic: runs every single time | Full: every execution is logged |
| Prompt reminder | ”Remember to check for security vulnerabilities” | Unreliable: depends on context and attention | None |
| Hook + prompt evaluation | PreToolUse hook uses Claude to evaluate safety | Deterministic trigger with intelligent evaluation | Full: both trigger and evaluation recorded |
Hooks do not replace good prompting; they complement it. You still want clear instructions in CLAUDE.md for guidance on how to approach tasks. But for anything that must happen, hooks are the enforcement mechanism.
The Hook Event System
Claude Code’s hook system supports 12+ event types that span the complete agent lifecycle [1]. Each event fires at a specific point, receives a specific payload, and can influence execution in specific ways.
PreToolUse
Fires before a tool call executes. This is your primary safety and gatekeeping event.
The payload includes tool_name (Bash, Write, Edit, Read, Glob, Grep, etc.) and tool_input (the parameters being passed to the tool). Your hook can return 3 possible decisions:
allow: Execute immediately without asking the userdeny: Block execution entirely, with an optional reason messageask: Require human confirmation before proceeding
If your hook returns nothing (just exits with code 0), execution proceeds normally through the standard permission system.

Figure 4 - PreToolUse Decision Control: Four possible outcomes from a PreToolUse hook. Allow bypasses the permission system for known-safe operations. Deny blocks dangerous actions with a reason the agent can see. Ask escalates to human confirmation for uncertain cases. No output preserves default behavior. This decision tree is the foundation of agent safety.
Common uses: Blocking destructive commands, enforcing file ownership boundaries (preventing agents from editing files outside their assigned directories), requiring approval for network requests, preventing writes to protected files like lockfiles or migration scripts.
PostToolUse
Fires after a tool call completes. This is your quality assurance and observability event.
The payload includes the tool name, inputs, and the tool’s output/result. Your hook can return additionalContext, a string that gets injected into the agent’s context, effectively giving the agent feedback on what just happened.
This is enormously powerful because it creates a feedback loop: the agent writes code, the hook runs the linter, and the linter’s output gets injected back into the agent’s context so it can fix any issues.

Figure 5 - The Quality Feedback Loop: PostToolUse hooks create automated self-correction. The agent writes code, the hook runs validators, errors flow back into context as additionalContext, and the agent fixes the issues, all without human intervention. This loop runs on every file write, creating continuous quality enforcement.
# PostToolUse: Run TypeScript compiler after file writesimport subprocess
result = subprocess.run( ["npx", "tsc", "--noEmit", "--pretty"], capture_output=True, text=True, timeout=30)
if result.returncode != 0: errors = result.stdout.strip().split("\n") error_count = len([l for l in errors if ": error TS" in l])
output = { "additionalContext": ( f"TypeScript errors detected: {error_count} error(s). " f"Fix these before continuing to the next task." ) } print(json.dumps(output))Common uses: Running linters, type checkers, and test suites after code changes; validating output format; logging tool usage for observability; injecting quality feedback into agent context.
The most powerful hook pattern is not deny/allow. It is injecting context that helps the agent self-correct. A PostToolUse hook that tells the agent “3 TypeScript errors found in handler.ts at lines 42, 78, and 103” is dramatically more useful than one that simply blocks the write. Feedback loops make agents better; gates just make them stop.
SessionStart and SessionEnd
SessionStart fires when a Claude Code session begins. Use it to inject initial context: the current project state, progress from previous sessions, feature lists, and environment configuration. SessionEnd fires when the session concludes. Use it to write progress summaries, commit work, update tracking files, and generate session reports [3].
These events are critical for the initializer + coding agent pattern for long-running tasks [3]. The SessionStart hook ensures every new session orients itself immediately rather than spending tokens figuring out what is going on.
PreCompact
Fires before Claude Code compacts (summarizes) the conversation context to free up token space. The payload includes what is about to be summarized and lost. This is one of the most underutilized hook events, but it is invaluable for observability. It reveals what information the agent is losing during long sessions, which is often the root cause of “agent confusion” in extended workflows.
Stop
Fires when an agent attempts to finish its session. Critically, a Stop hook can block the agent from stopping if completion criteria are not met. This is how you prevent agents from declaring victory before all features are implemented or all tests pass.
Other Events
SubagentStart and SubagentStop track the lifecycle of parallel agents in a team. UserPromptSubmit fires when the user sends a message. Notification fires when Claude wants to notify the user. PermissionRequest fires when Claude requests permission for an action.

Figure 6 - The Complete Hook Event Lifecycle: Every hook event mapped to its position in the agent session. PreToolUse and PostToolUse fire on every tool call (the inner loop). SessionStart and Stop fire once each (session boundaries). PreCompact fires when context needs trimming. SubagentStart/Stop track parallel agent lifecycles. Together, they provide deterministic control across the entire agent lifecycle.
The Three Hook Types
Hooks are not limited to running shell scripts. Claude Code supports 3 distinct hook types, each suited to different validation needs [1].
Command Hooks
The most straightforward type. A shell command runs, reads stdin, optionally writes to stdout, and exits. Deterministic, fast, and predictable. Use these for structural validation, linting, format checking, and event forwarding.
{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validators/lint_on_save.sh"}Prompt-Based Hooks
A prompt-based hook sends the event context to a Claude model for single-turn evaluation. The model reads the context, applies judgment, and returns a decision. This gives you intelligent evaluation without the overhead of a full agent session. Use these when the validation requires understanding intent or semantics rather than just pattern matching [1].
{ "type": "prompt", "prompt": "Review this computation output. Does it contain valid risk metrics? Are the numbers in reasonable ranges? Flag anything implausible."}Agent-Based Hooks
The most powerful type. An agent-based hook spawns a Claude model with tool access for multi-turn verification. The evaluator agent can read files, run commands, and perform complex analysis before returning its assessment. Use these for comprehensive review gates [1].
{ "type": "agent", "prompt": "Review this agent's complete output. Verify all portfolio positions are covered and hedging recommendations include cost estimates. Block completion if anything is missing.", "tools": "Read, Bash"}
Figure 7 - Three Hook Types: From Deterministic to Intelligent: Command hooks are fast and deterministic, ideal for safety gates and structural validation. Prompt-based hooks add intelligent evaluation for semantic analysis. Agent-based hooks provide thorough multi-step verification for comprehensive review gates. Use deterministic hooks for safety; intelligent hooks for quality.
Agent-based hooks are particularly powerful as Stop hooks. They can prevent an agent from declaring its work complete until a thorough review confirms quality standards are met.
Do not use prompt-based hooks for safety boundaries. Prompt-based hooks use an LLM for evaluation, which means they are probabilistic. For hard safety constraints (blocking destructive commands, enforcing file ownership), use deterministic command hooks. Reserve prompt-based and agent-based hooks for quality assessment where intelligent judgment adds value.
Embedding Hooks in Agent and Skill Definitions
One of the most important developments is the ability to embed hooks directly in agent definitions and skill definitions, rather than only in the global settings.json. This enables per-agent validation: each agent gets hooks tailored to its specific role and responsibilities.
Consider why this matters. In a team of agents where one handles CSV data processing and another handles API development, a global hook that validates CSV structure on every file write would be wasteful for the API agent. Conversely, the API agent needs OpenAPI spec validation that would be irrelevant for the CSV agent. Per-agent hooks solve this by co-locating the validation logic with the agent definition.

Figure 8 - Global Hooks vs Per-Agent Hooks: Global hooks apply the same validation to every agent, creating waste and noise when agents have different roles. Per-agent hooks embedded in agent definitions apply only relevant validation to each specialist. The CSV analyst validates data structure. The API developer validates OpenAPI specs. Each agent carries exactly the validation it needs.
Here is an agent definition with embedded hooks:
---name: risk-monitordescription: Assesses portfolio risk exposure and recommends hedging strategies.tools: Read, Write, Bash, Glob, Grepmodel: opushooks: PostToolUse: - matcher: "Write" hooks: - type: command command: "uv run $CLAUDE_PROJECT_DIR/.claude/hooks/validators/validate_risk_metrics.py" - matcher: "Bash" hooks: - type: prompt prompt: | Review this computation output. Does it contain valid risk metrics? Are the numbers in reasonable ranges? Flag anything implausible. Stop: - matcher: "*" hooks: - type: agent prompt: | Review this agent's complete output. Verify all portfolio positions are covered and hedging recs include cost estimates. Block completion if anything is missing. tools: Read, Bashcolor: red---
# Risk Monitor AgentYou are a portfolio risk specialist...Four Architectural Patterns
Based on research and community implementations, 4 distinct hook architecture patterns have emerged for production use.
Pattern 1: Safety Layer
The safety layer uses PreToolUse hooks to create hard boundaries around what agents can do.
Agent Decision → PreToolUse Hook → Safety Check → Allow/Deny → Tool Execution
Figure 9 - The Safety Layer Pattern: PreToolUse hooks create hard boundaries through sequential checks: destructive command detection, file ownership enforcement, and protected file gates. The agent cannot bypass these checks regardless of its reasoning. When denied, the reason is injected into context so the agent can adapt its approach.
#!/usr/bin/env python3"""Safety hook: Enforce file ownership boundaries for team agents."""import jsonimport sysimport os
OWNERSHIP = { "frontend-dev": ["src/components/", "src/pages/", "src/styles/"], "backend-dev": ["src/server/", "src/api/", "src/database/"], "sync-engine": ["src/sync/", "src/crdt/", "src/websocket/"],}
def main(): input_data = json.loads(sys.stdin.read()) tool_name = input_data.get("tool_name", "")
if tool_name not in ("Write", "Edit"): sys.exit(0)
file_path = input_data.get("tool_input", {}).get("file_path", "") agent_name = os.environ.get("CLAUDE_AGENT_NAME", "")
if agent_name not in OWNERSHIP: sys.exit(0)
allowed_dirs = OWNERSHIP[agent_name] if not any(file_path.startswith(d) for d in allowed_dirs): output = { "decision": "deny", "reason": ( f"Agent '{agent_name}' cannot write to '{file_path}'. " f"Allowed directories: {', '.join(allowed_dirs)}." ) } print(json.dumps(output))
sys.exit(0)
if __name__ == "__main__": main()Pattern 2: Quality Feedback Loop
The quality loop uses PostToolUse hooks to create automated feedback that flows back into agent context.
Tool Execution → PostToolUse Hook → Quality Check → additionalContext → Agent AdaptsThis is the pattern that transforms hooks from passive gates into active participants in the development process. When a linter catches an error, the error description flows back to the agent as additionalContext, and the agent fixes the issue in its next action, without human intervention.
Pattern 3: Observability Pipeline
The observability pattern uses hooks across all event types to feed a monitoring system. Every PreToolUse forwards the agent’s intention. Every PostToolUse forwards the result. PreCompact captures what context is being lost. SubagentStart and SubagentStop track team composition.
This pattern provides full lifecycle coverage across all 13 hook events, feeding a real-time dashboard that visualizes agent behavior patterns.

Figure 10 - The Observability Pipeline: Every hook event feeds a monitoring system. Agent intentions (PreToolUse), results (PostToolUse), context loss (PreCompact), session lifecycle (SessionStart/Stop), and team composition (SubagentStart/Stop) all flow into a real-time dashboard. When an agent swarm misbehaves, this event log is your primary debugging tool.
{ "hooks": { "PreToolUse": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/observability/send_event.py --event pre_tool_use" }] }], "PostToolUse": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/observability/send_event.py --event post_tool_use" }] }], "PreCompact": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/observability/log_compaction_loss.py" }] }] }}Pattern 4: Completion Gates
The completion gate uses Stop hooks to prevent agents from finishing prematurely. This is critical for long-running tasks where an agent might declare victory before all features are implemented or all tests pass.

Figure 11 - The Completion Gate Pattern: Stop hooks prevent agents from finishing prematurely. When the agent tries to stop, the hook checks completion criteria: are all features passing? Do all tests pass? Is the progress file updated? If any check fails, the agent is blocked from stopping and must continue working. This ensures agents cannot declare victory without verification.
#!/bin/bash# Stop hook: Validate all features pass before allowing agent to finishINPUT=$(cat)
INCOMPLETE=$(jq '[.[] | select(.passes == false)] | length' feature_list.json)
if [ "$INCOMPLETE" -gt 0 ]; then echo "{\"decision\": \"block\", \"reason\": \"$INCOMPLETE features still incomplete. Cannot finish yet.\"}" exit 2 # Exit code 2 blocks the stopfi
exit 0 # All features complete, allow stopBest Practices
Start with hooks before scaling to teams. Get your validation, safety, and observability infrastructure working with a single agent before parallelizing. The chaos of multi-agent systems is only manageable with deterministic control already in place.
Match hooks to the agent’s purpose. Per-agent hooks embedded in agent definitions are dramatically more effective than global hooks. A CSV agent needs CSV validation; a build agent needs linting. Do not apply the same hooks to every agent; it wastes computation and creates noise.
Keep hook scripts fast. Hooks execute synchronously. A slow hook blocks the agent. Linting a single file should take under 5 seconds. If you need heavyweight analysis, use it only on Stop hooks (which run once) rather than PostToolUse hooks (which run on every tool call).
Use additionalContext for feedback beyond simple blocking. The most powerful hook pattern is not deny/allow. It is injecting context that helps the agent self-correct. A PostToolUse hook that tells the agent “3 TypeScript errors found in line 42, 78, and 103” is more useful than one that simply blocks the write.
Log everything in production. Forward all hook events to an observability endpoint. When an agent swarm misbehaves, the hook event log is your primary debugging tool.
Use PreCompact hooks. This is the most underutilized event type, but it reveals critical information about what the agent is forgetting during long sessions. If your agents seem confused after extended work, PreCompact logs will show you what context was lost.

Figure 12 - The Complete Hooks Architecture: All 4 patterns working together: safety (PreToolUse gates), quality (PostToolUse feedback loops), observability (all-event monitoring), and completion (Stop gates). Command hooks enforce safety deterministically. Prompt and agent hooks provide intelligent quality evaluation. Observability hooks capture everything. Together, they make autonomous agents safe, reliable, and debuggable.
Conclusion
Hooks represent a fundamental architectural insight: autonomous AI agents need deterministic control layers that operate outside the LLM’s reasoning chain. The agent can be creative, adaptive, and autonomous in its problem-solving while hooks enforce the non-negotiable constraints: safety boundaries, quality standards, workflow requirements, and observability guarantees.
The combination of the 3 hook types (command, prompt-based, and agent-based) with per-agent embedding creates a control system that is both rigid where it needs to be (blocking destructive actions) and intelligent where it helps (evaluating code quality with LLM judgment). As agent teams grow larger and more autonomous, hooks become the trust infrastructure that makes scaling possible.
Autonomous agents need deterministic control layers that operate outside the LLM’s reasoning chain. The agent reasons creatively about how to solve problems. Hooks enforce the non-negotiable constraints (safety, quality, workflow, observability) without depending on the agent’s attention, context window, or compliance. This separation of concerns is what makes production agent systems trustworthy.
The strongest agent systems do not choose between autonomy and control. They use prompts for guidance and hooks for enforcement. They use creative reasoning for problem-solving and deterministic validation for quality. The agent is free to be intelligent. The hooks make sure it is also reliable.
The Series
This is Part 4 of a 6-part series on Claude Code:
- Orchestrating AI Agent Teams — The control layer architecture that makes autonomous coding reliable
- Building Effective Claude Code Agents — Agent definitions, tool restrictions, and least privilege
- Claude Code Skills — Progressive disclosure and reusable knowledge packages
- Claude Code Hooks (this article) — PreToolUse, PostToolUse, and deterministic enforcement
- Claude Code Agent Teams — Multi-agent coordination and file ownership
- Claude Code Security — Defense-in-depth with agents, skills, hooks, commands, and teams
References
[1] Anthropic, “Automate workflows with hooks,” Claude Code Documentation, 2025. https://code.claude.com/docs/en/hooks-guide
[3] J. Young et al., “Effective harnesses for long-running agents,” Anthropic Engineering Blog, Nov 2025. https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents
[6] E. Schluntz and B. Zhang, “Building effective agents,” Anthropic Engineering Blog, Dec 2024. https://www.anthropic.com/engineering/building-effective-agents
[7] Anthropic, “Orchestrate teams of Claude Code sessions,” Claude Code Documentation, 2025. https://code.claude.com/docs/en/agent-teams
[8] Anthropic, “Extend Claude Code,” Claude Code Documentation, 2025. https://code.claude.com/docs/en/features-overview
[9] N. Carlini, “Building a C compiler with a team of parallel Claudes,” Anthropic Engineering Blog, Feb 2025. https://www.anthropic.com/engineering/building-c-compiler
[10] Anthropic, “Skill authoring best practices,” Claude Platform Documentation, 2025. https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices
[11] A. Osmani, “Claude Code Swarms,” AddyOsmani.com, Feb 2026. https://addyosmani.com/blog/claude-code-agent-teams/
[12] Anthropic, “Create plugins,” Claude Code Documentation, 2025. https://code.claude.com/docs/en/plugins