AI

CCA-F Study Day 4/20: The Hooks System — Deterministic Enforcement

🎯 CCA-F Study Day 4/20: The Hooks System — Deterministic Enforcement

Domain 1: Agentic Architecture & Orchestration (~25-27% of exam)

📌 Today's Focus

Today you master the Hooks System — the mechanism that separates advisory instructions from guaranteed enforcement. This is one of the most heavily tested concepts on the exam because it directly addresses the #1 anti-pattern: using prompts to enforce critical business rules. The exam will present scenarios where you must choose between prompt-based guidance and programmatic hooks, and knowing the difference between "Claude should" and "Claude must" is the key to getting these questions right.

📚 Core Concepts

  1. What Are Hooks?

Hooks are callback functions (shell commands, HTTP endpoints, MCP tools, prompts, or agents) that run YOUR code in response to specific agent lifecycle events. The critical distinction:

CLAUDE.md instructions = advisory (the model should follow them, but it's non-deterministic)

Hooks = deterministic (your code runs regardless of what the model decides — guaranteed enforcement)

This is the single most important sentence for the exam: "Unlike CLAUDE.md instructions which are advisory, hooks are deterministic and guarantee the action happens."

  1. Hook Event Types (Full Lifecycle)

The exam tests your knowledge of WHEN each hook fires. Here's the complete set:

Hook Event When It Fires Can Block? Use Case

SessionStart Session begins/resumes No Load dev context, set env vars

PreToolUse Before a tool call executes Yes Block dangerous commands, modify inputs

PostToolUse After a tool succeeds No (feedback only) Audit logging, transform outputs

PostToolUseFailure After a tool fails No Error handling, alerting

PostToolBatch After full batch of parallel tool calls Yes Cross-tool validation

Stop Agent finishes responding Yes Enforce completion criteria

SubagentStart Subagent spawned No Inject context, track parallel tasks

SubagentStop Subagent finishes Yes Validate subagent output

PreCompact Before context compaction Yes Archive full transcript

Notification Agent status messages No Forward to Slack/PagerDuty

PermissionRequest Permission dialog shown Yes Auto-approve/deny specific operations

TaskCreated Task being created Yes Enforce naming conventions

TaskCompleted Task being marked done Yes Require tests to pass

  1. Hook Handler Types (5 Types)

The exam expects you to know all five hook handler types:

Command hooks (type: "command") — Shell scripts that receive JSON on stdin

HTTP hooks (type: "http") — POST to a URL endpoint

MCP tool hooks (type: "mcp_tool") — Call a tool on an MCP server

Prompt hooks (type: "prompt") — Send a prompt to Claude for single-turn evaluation

Agent hooks (type: "agent") — Spawn a subagent to verify conditions

  1. Permission Decision Priority

When multiple hooks apply, priority is: deny > defer > ask > allow

If ANY hook returns deny, the operation is blocked regardless of what other hooks return. This is critical for understanding how enforcement layers compose.

  1. The PreToolUse Decision Control (Most Tested)

PreToolUse is the most exam-relevant hook because it's where enforcement happens. Four possible decisions:

"allow" — Skip the permission prompt, proceed

"deny" — Block the tool call entirely

"ask" — Show a confirmation prompt to the user

"defer" — Pause execution (for -p mode integrations)

  1. Configuration Locations & Scope

Location Scope Shareable?

~/.claude/settings.json All your projects No (local)

.claude/settings.json Single project Yes (committable)

.claude/settings.local.json Single project No (gitignored)

Managed policy settings Organization-wide Yes (admin-controlled)

Plugin hooks/hooks.json When plugin enabled Yes (bundled)

⚠️ Anti-Patterns & Exam Traps

The #1 Anti-Pattern: Prompt-Based Enforcement

❌ Wrong Answer (Exam Trap) ✅ Correct Approach Why

"Add to system prompt: Never delete production files" Use a PreToolUse hook that blocks rm commands on production paths Prompts are advisory; hooks are deterministic

"Tell Claude to always log actions" Use a PostToolUse hook for audit logging Logging must happen regardless of model behavior

"Instruct the model to reject PII" Use a PreToolUse hook with PII regex checking Compliance cannot depend on model judgment

"Add a note in CLAUDE.md saying 'always validate inputs'" Write a hook that programmatically validates inputs before tool execution CLAUDE.md is advisory — critical validation needs guarantees

Other Traps to Watch For:

❌ Confusing "hooks" with "tools" — Hooks intercept the agent lifecycle; tools are what the agent calls

❌ Exit code 1 blocks the action — WRONG. Only exit code 2 blocks. Exit 1 is a non-blocking error

❌ PostToolUse can prevent the tool from running — WRONG. The tool already ran. PostToolUse only provides feedback

❌ Hooks replace CLAUDE.md — WRONG. They complement each other. CLAUDE.md for guidance, hooks for enforcement

💻 Code Examples

Example 1: Block Destructive Commands (PreToolUse)

#!/bin/bash

# .claude/hooks/block-destructive.sh

# This is a PreToolUse hook matching "Bash" tools

INPUT=$(cat) # Read JSON from stdin

COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')

# Block rm -rf, DROP TABLE, etc.

if echo "$COMMAND" | grep -qE 'rm -rf|DROP TABLE|truncate|format'; then

# Exit code 2 = BLOCKING error

echo "Destructive command blocked by compliance hook" >&2

exit 2

fi

# Exit 0 = allow the command

exit 0

Example 2: Structured JSON Decision (PreToolUse)

#!/bin/bash

# .claude/hooks/protect-env-files.sh

INPUT=$(cat)

FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')

if [[ "$FILE_PATH" == *.env* ]]; then

# Return structured JSON decision

jq -n '{

hookSpecificOutput: {

hookEventName: "PreToolUse",

permissionDecision: "deny",

permissionDecisionReason: "Cannot modify .env files — use secrets manager instead"

}

}'

else

exit 0 # Allow

fi

Example 3: Audit Logging (PostToolUse)

#!/bin/bash

# .claude/hooks/audit-log.sh

# Fires after Write|Edit tools succeed

INPUT=$(cat)

TOOL=$(echo "$INPUT" | jq -r '.tool_name')

FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path')

TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

# Deterministic logging — happens regardless of model behavior

echo "{\"timestamp\": \"$TIMESTAMP\", \"tool\": \"$TOOL\", \"file\": \"$FILE\"}" >> ~/.claude/audit.jsonl

exit 0

Example 4: Hook Configuration in settings.json

{

"hooks": {

"PreToolUse": [

{

"matcher": "Bash",

"hooks": [

{

"type": "command",

"if": "Bash(rm *)",

"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-destructive.sh",

"args": []

}

]

},

{

"matcher": "Write|Edit",

"hooks": [

{

"type": "command",

"if": "Write(*.env)|Edit(*.env)",

"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/protect-env-files.sh",

"args": []

}

]

}

],

"PostToolUse": [

{

"matcher": "Write|Edit",

"hooks": [

{

"type": "command",

"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/audit-log.sh",

"args": []

}

]

}

]

}

}

Example 5: HTTP Hook for External Compliance Service

{

"hooks": {

"PreToolUse": [

{

"matcher": "Bash",

"hooks": [

{

"type": "http",

"url": "http://localhost:8080/hooks/validate-command",

"timeout": 30,

"headers": {

"Authorization": "Bearer $COMPLIANCE_TOKEN"

},

"allowedEnvVars": ["COMPLIANCE_TOKEN"]

}

]

}

]

}

}

🎬 Video to Watch

Claude Code Advanced Patterns: Subagents, MCP, and Scaling to Real Codebases — Anthropic webinar covering hooks configuration, subagent patterns, and real-world scaling. Focus on the hooks section where they demonstrate how teams use PreToolUse hooks for compliance enforcement in production deployments.

📖 Reading

Primary: Claude Code Hooks Reference — The full official documentation covering all hook events, configuration, and decision control

Secondary: Automate Workflows with Hooks (Guide) — Practical walkthrough with real examples

Context: Building Agents with the Claude Agent SDK — Engineering blog post covering hooks in the broader agent architecture

🛠️ Hands-On Exercise (20 minutes)

Build a compliance enforcement hook system:

Create a project directory with .claude/hooks/ and .claude/settings.json

Write a PreToolUse hook (compliance-check.sh) that:

Blocks any Bash command containing rm -rf

Blocks any Write/Edit to files in a config/secrets/ directory

Logs all blocked attempts to an audit file with timestamp and reason

Write a PostToolUse hook (audit-log.sh) that logs every successful file write to ~/.claude/audit.jsonl with the tool name, file path, and timestamp

Configure both hooks in .claude/settings.json with correct matchers and if conditions

Test mentally: What happens when Claude tries to rm -rf /tmp/build? What exit code blocks it? What happens if your hook exits with code 1 instead of 2?

📝 Quick Quiz

Question 1: A financial services company needs to ensure that no agent can ever execute a database DELETE command in production. Which approach provides the strongest guarantee?

A) Add "Never execute DELETE commands in production" to the system prompt

B) Include a rule in CLAUDE.md: "Production DELETE commands are prohibited"

C) Implement a PreToolUse hook that inspects Bash commands and returns exit code 2 for any containing DELETE

D) Set max_turns to 1 to prevent the agent from taking destructive actions

Question 2: When multiple PreToolUse hooks apply to the same tool call and return different decisions, what is the resolution order?

A) First hook registered wins

B) allow > ask > deny > defer

C) deny > defer > ask > allow

D) The most recently modified hook takes precedence

Question 3: A hook script returns exit code 1 on a PreToolUse event. What happens?

A) The tool call is blocked and the error message is shown to Claude

B) The tool call proceeds — exit code 1 is a non-blocking error

C) The entire agent session terminates

D) Claude retries the tool call with modified parameters

Answers:

  1. C — PreToolUse hooks are deterministic. Prompts and CLAUDE.md are advisory (the model might ignore them). max_turns doesn't address the specific threat.

  2. C — deny > defer > ask > allow. If ANY hook denies, the action is blocked regardless of others.

  3. B — Only exit code 2 blocks. Exit code 1 is a non-blocking error — the tool call still proceeds, and stderr is logged. This is a common exam trap.

👀 Tomorrow's Preview

Day 5 covers Task Decomposition & Exam Scenarios — the final Domain 1 day where you'll put everything together with the Customer Support and Multi-Agent Research exam scenarios, focusing on error boundaries, context isolation, and how to design escalation logic based on structured criteria.