3213 words
16 minutes
Claude Code Hooks: The Deterministic Control Layer for AI Agents
Part 4 of 5 Claude Code

Suppose your CLAUDE.md file has instructions to always run the linter (the error analyzer). Your agent follows this instruction most of the time, but when a project is long, requires many sessions, and complex debugging that takes up the context window, the agent doesn’t run it and misses the bug.

With hooks, a PostToolUse hook fires after every file write, running the linter. If there are errors, it injects the error descriptions back into the agent’s context as additionalContext and the agent fixes the issue in its next action. This setup ensures that the linter will always run, regardless of project length, sessions, and space in the context window. That is the difference between probabilistic compliance and deterministic control; with hooks, you have reproducible, auditable, and governable agent behavior.

Claude Code Hooks are user-defined shell commands that execute at specific points in Claude Code’s lifecycle. They are not prompts; 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.

Probabilistic vs Deterministic

Figure 1 - 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.

How Hooks Work#

At their core, hooks are shell commands that Claude Code executes at predefined points during its operation. 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.

Hook Execution Flow

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.

This PreToolUse hook blocks any Bash command containing destructive patterns:

#!/usr/bin/env python3
"""PreToolUse hook: Block dangerous shell commands."""
import json
import 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.

Prompts vs Hooks: The Reliability Comparison#

This distinction is worth emphasizing because it is the core architectural insight behind hooks.

Prompts vs 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.

ApproachMechanismReliabilityAuditability
CLAUDE.md instruction”Always run the linter after writing code”Probabilistic: usually complies but may skipNone unless you check manually
HookPostToolUse hook runs linter after every file writeDeterministic: runs every single timeFull: every execution is logged
Prompt reminder”Remember to check for security vulnerabilities”Unreliable: depends on context and attentionNone
Hook + prompt evaluationPreToolUse hook uses Claude to evaluate safetyDeterministic trigger with intelligent evaluationFull: 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. Each event fires at a specific point, receives a specific payload, and can influence execution in specific ways.

PreToolUse#

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 user
  • deny: Block execution entirely, with an optional reason message
  • ask: Require human confirmation before proceeding

If your hook returns nothing (just exits with code 0), execution proceeds normally through the standard permission system.

PreToolUse Decision Tree

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, and no output preserves default behavior.

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#

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.

PostToolUse Feedback Loop

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.

Here’s an example of PostToolUse:

# PostToolUse: Run TypeScript compiler after file writes
import 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, and injecting quality feedback into agent context.

KEY INSIGHT: 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.

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.

These events are critical for the initializer + coding agent pattern for long-running tasks. The SessionStart hook ensures every new session orients itself immediately rather than spending tokens figuring out what is going on.

PreCompact#

PreCompact ires before Claude Code 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#

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.

Hook Event Lifecycle

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.

Command Hooks#

The most straightforward type. A shell command runs, reads stdin, optionally writes to stdout, and exits. It’s 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.

{
"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#

This is the most powerful type of hook. 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.

{
"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"
}

Three Hook Types

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 and intelligent hooks for quality.

KEY INSIGHT: 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, meaning each agent gets hooks tailored to its specific role and responsibilities.

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.

Global vs Per Agent Hooks

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.

Here is an agent definition with embedded hooks:

.claude/agents/team/risk-monitor.md
---
name: risk-monitor
description: Assesses portfolio risk exposure and recommends hedging strategies.
tools: Read, Write, Bash, Glob, Grep
model: opus
hooks:
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, Bash
color: red
---
# Risk Monitor Agent
You 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

Safety Layer Pattern

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.

Here is an example of using safety hooks:

#!/usr/bin/env python3
"""Safety hook: Enforce file ownership boundaries for team agents."""
import json
import sys
import 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 Adapts

This 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.

{
"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.

Completion Gate Pattern

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 finish
INPUT=$(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 stop
fi
exit 0 # All features complete, allow stop

Best 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. Do not apply the same hooks to every agent as 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 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.

Complete Hook Architecture

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 like 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.

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 1 of a 5 part series on Claude Code:

  1. Claude Autonomous Coding Overview --- The control layer architecture that makes coding reliable
  2. Building Effective Claude Code Agents: From Definition to Production --- Agent definitions, tool restrictions, and least privilege
  3. Claude Code Skills: Building Reusable Knowledge Packages for AI Agents --- Progressive disclosure and reusablel knowledge packets
  4. Claude Code Hooks: The Deterministic Control Layer for AI Agents (this article) --- PreToolUse, PostToolUse, and deterministic enforcement
  5. Claude Code Agent Teams: Building Coordinated Swarms of AI Developers --- 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

[2] Disler, “Claude Code Hooks Multi-Agent Observability,” GitHub Repository, 2025. https://github.com/disler/claude-code-hooks-multi-agent-observability

[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

[4] Disler, “Agentic Finance Review,” GitHub Repository, 2025. https://github.com/disler/agentic-finance-review

[5] Disler, “Claude Code Hooks Mastery,” GitHub Repository, 2025. https://github.com/disler/claude-code-hooks-mastery

[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

Claude Code Hooks: The Deterministic Control Layer for AI Agents
https://katrina.dotzlaw.com/articles/claude/hooks/
Author
Katrina Dotzlaw, Ryan Dotzlaw, Gary Dotzlaw
Published at
2026-02-24
License
CC BY-NC-SA 4.0
← Back to Articles