Part 2 · Guardrails

Harness Engineering · ~7 min

Hooks — Deterministic Guardrails

An instruction is a "should-do" the model can reason around. A hook is a "must-do" the harness runs no matter what the model decides.

Why this, for you: this is Pillar 2 — mechanical enforcement — made concrete. The moment a rule matters enough that "usually followed" isn't good enough (formatting, secret-blocking, dangerous-command guards), a hook makes it deterministic. It's the single most reliable lever in the whole harness.

"Don't break any links" in your AGENTS.md is a prompt — probabilistic, sometimes ignored. A link checker that runs on every commit is a guardrail — deterministic, always runs, cannot be reasoned around. Hooks build the second kind.

1 Why hooks are deterministic

The guarantee comes from who runs them. The harness — not the model — invokes a hook at a fixed point in the request loop, independent of any sampling. A PreToolUse hook fires before the tool call dispatches; a PostToolUse hook fires after the result returns. The model never gets a vote.

For PreToolUse, PermissionRequest, UserPromptSubmit, and Stop, exit code 2 blocks the action and feeds the hook's stderr back to the model as feedback. The block is mechanical — the action does not happen.

Claude Code fires 25+ hook events across the session, prompt, tool, subagent, compaction, worktree, and file-change lifecycles. The load-bearing few:

EventWhen it fires
PreToolUseBefore a tool call — exit 2 blocks it
PostToolUseAfter a tool call succeeds — good for format/lint
UserPromptSubmitWhen a prompt is submitted, before processing
Stop / SubagentStopWhen a turn (or subagent) ends — a completion gate (Lesson 06)
SessionStart / SessionEndSession boundaries — load or snapshot state

2 The shape of a hook

Hooks live in settings.json under a hooks object keyed by event name. Each matcher pairs a tool-name pattern ("Bash", "Write|Edit", or a regex) with a handler. Hook input arrives on stdin as JSON — scripts pipe it through jq to read tool_input fields.

# block-dangerous-commands.sh — a PreToolUse hook on Bash COMMAND=$(jq -r '.tool_input.command' < /dev/stdin) for pattern in "rm -rf /" "dd if=/dev"; do if echo "$COMMAND" | grep -qF "$pattern"; then echo "Blocked: '$pattern' is not permitted" >&2 exit 2 # cancels the call, sends stderr to the model fi done

Three hooks compose a non-negotiable rule set: block dangerous shell commands before they run (PreToolUse), auto-format every file after it's written (PostToolUse on Write|Edit), and snapshot progress when the session ends (SessionEnd). None of them depends on the model remembering.

3 Hooks vs. prompts — and when a hook is wrong

The dividing line is simple. Use a hook when compliance is non-negotiable and the rule is checkable — formatting, security, validation. Use a CLAUDE.md instruction when the rule bends gracefully under load — a style preference, a phrase to avoid. A prompt fails soft and keeps the model in the loop; a hook fails hard and stops it.

Hooks have failure modes too

Hidden global state: a misbehaving agent is often a hook silently rewriting or blocking files — hooks are invisible in the transcript unless they exit non-zero, so debugging can take longer than the hook saves. Over-scoped blocks: a substring check for rm also blocks rm node_modules and echo "term", pushing users to disable the hook entirely. Performance tax: every PostToolUse hook runs synchronously on the hot path — a slow formatter inflates every edit. Keep them tight, scoped, and fast; move heavy work to SessionEnd.

↪ Your win: make the rules that matter mechanical

Retrieval practice — recall, don't peek

Question 1Hooks are deterministic because they're run by…

Question 2In a PreToolUse hook, exit code 2…

Question 3A hook script reads the tool's arguments from…

Question 4You should prefer a CLAUDE.md instruction over a hook when the rule…

Question 5 · spaced recall from Lesson 02The recommended shape for AGENTS.md is…

Ask me anything. Want a starter settings.json with a secret-scanner and an auto-formatter, or the full list of which events honor exit-code-2 blocking? Next: Sub-Agents & Orchestration — isolating work into fresh context windows.
✎ Feedback