Part 6 · Beyond the Tool Catalog

Tool Engineering · ~7 min

Hooks & Deterministic Lifecycle Enforcement

Lesson 7 made effects safe by designing the tool to be re-runnable. This lesson makes them safe a second way — by intercepting the call before it runs, with code the model cannot reason its way around.

Why this, for you: some rules cannot be left to the model. "Never push to main", "use pnpm, not npm", "no writes to the secrets file" — these are non-negotiable, and a prompt only requests them. A hook requires them. Knowing which rules belong in a hook versus a description is the last reliability decision the course hands you.

Every prior move lived inside the model's reasoning: a better description, a clearer error, an idempotency guard the tool itself enforces. Hooks step outside it. A hook is deterministic automation the harness — not the model — runs at a fixed point in the request loop, independent of any sampling.

Prompts request behavior; hooks require it. A prompt instruction is a probabilistic "should-do" that degrades under task pressure toward the model's training defaults. A hook is a deterministic "must-do" that runs as code outside the agent's context — the model cannot overrule it through reasoning.

1 Where hooks fire, and which can block

Hooks attach to lifecycle events. The harness invokes a PreToolUse hook before dispatching a tool call and a PostToolUse hook after the result returns — the same loop, every time. The split is the whole design: one enforces, one automates.

EventWhenRole
PreToolUseBefore a tool call dispatchesEnforces — exit 2 blocks the call
PostToolUseAfter the result returnsAutomates — lint, format, audit
UserPromptSubmitUser submits, before processingEnforces — can block
SessionStart / StopSession or turn boundarySnapshot, notify, gate

Hook input arrives on stdin as JSON, so scripts pipe it through jq to read fields like tool_input.command. For PreToolUse, exit code 2 cancels the call and feeds stderr back to the model as the reason to adapt. For post-tool events the action already ran, so exit 2 only surfaces feedback — it can't un-ring the bell.

# PreToolUse(Bash) — block npm, redirect to pnpm. Input is stdin JSON. COMMAND=$(jq -r '.tool_input.command' < /dev/stdin) if echo "$COMMAND" | grep -qE '^npm (install|i |ci )'; then echo "Use pnpm instead of npm" >&2 exit 2 # cancels the call; stderr becomes the model's redirect fi

2 The decision rule: hook or prompt?

This is the lesson's core judgment. Reach for a hook only when all three hold; otherwise the rule stays in the prompt.

Use a hook when…Use a prompt when…
Compliance is non-negotiable — failure has real costGuidance is contextual ("prefer X when in Y")
The rule is binary — a call either violates it or doesn'tApplying the rule needs model judgment
There's a strong opposing training prior (e.g. npm over pnpm)Over-blocking's false positives cost more than rare misses

Hooks see parameters, not intent. They can't tell a legitimate git push --force on a personal branch from a dangerous one aimed at main — so architectural guidance, quality standards, and situational judgment stay in the prompt, where the model evaluates context a hook can't inspect. This is the same line Lesson 6 drew between a prescriptive sequence and high-level prompting, now at the enforcement boundary.

Injection resistance and zero context cost

A hook buys two things a prompt cannot. First, immunity to prompt injection: an injected instruction can influence what the agent tries, never what a PreToolUse hook allows — the gate fires before execution, outside the reasoning loop. Second, it has zero context cost: moving an absolute rule out of CLAUDE.md and into a hook frees the tokens it occupied and removes one more instruction competing for attention.

3 The four boundaries where hooks fail

Determinism at the tool-call boundary is not determinism everywhere. A hook that you trust to be absolute can be quietly evaded:

So the deny must cover every tool that achieves the same effect, and the truly hard boundary belongs in OS-level controls — file permissions, network policy, containers — not a single matcher.

↪ Your win: enforce the non-negotiable, prompt the rest

Retrieval practice — recall, don't peek

Question 1A hook is deterministic because it is run by the…

Question 2To cancel a tool call from a PreToolUse hook, the script must…

Question 3A rule belongs in a prompt rather than a hook when applying it…

Question 4A hook that blocks rm but not a truncating Write has failed at the boundary of…

Question 5 · spaced recall from Lesson 12The frontmatter field that makes a side-effect skill user-only, so the model can't fire it on its own timing, is…

Ask me anything. Want to sort a list of your team's rules into hook-enforced versus prompt-guided, or see why a substring matcher on rm blocks rm node_modules and breaks more than it guards? Next in Part 6: The Unix CLI as a Tool Interface — when one run(command) tool replaces the catalog.
✎ Feedback