---
title: "Hooks"
description: "Attach a check before or after tool execution. Change the event and matcher to see a protected write prevented, reported afterward, or missed entirely."
source: https://vizlearn.app/ai/agents/hooks
access: premium (interactive player requires Lifetime; this doc is free)
---

# Hooks

Hooks are callbacks that an agent's runtime invokes at selected lifecycle events. A matching pre-tool hook can veto a proposed call before it changes a file. A post-tool hook can inspect the completed result, but cannot prevent an action that already happened. Both the event and tool matcher must match for the callback to run.

Think of a checkpoint on a route. A check before the tool can stop the request from reaching it. A check after the tool can record what happened. Moving the checkpoint changes what it can prevent, even when the check still recognizes the same problem.

## How Hooks Work

Use a pre-tool check when you need to prevent an action. Use a post-tool callback when you need a successful result, for example to log a completed edit or trigger formatting. The runtime that dispatches these events and runs the tools is the agent's **harness**.

A registration connects three things: an **event**, a **matcher**, and a **callback**. In this simulation, h1 registers `check_release` at either `PreToolUse` or `PostToolUse`. Its matcher selects `Edit`, or both `Edit|Write`. The dispatcher first checks the event, then the tool name. Only a double match invokes the callback. The callback separately examines the target file.

The model is outside this part of the process. The two requests are already provided; the harness owns dispatch and execution. No additional model decision chooses whether to run `check_release`.

Our callback protects `release.txt`. At a pre-event it records an attempted call, then returns `deny` for that target or `no-decision` for another file. The harness consumes that output before execution. A no-decision result falls through to the fixture's permissive baseline policy; it does not grant permission on its own.

At a post-event, the same event-aware callback receives a successful tool result, records the completed call, and reports a violation if the target is protected. The report accompanies the tool result. This callback contains no compensating write, so it leaves the file as the tool wrote it.

These event names follow [Claude Code's hooks reference](https://code.claude.com/docs/en/hooks): pre hooks run before execution, post-success hooks receive results, and matching filters the tool name. Event-specific output formats differ. For example, a post-hook block report adds feedback; it cannot retroactively prevent the tool's side effects. The compact output labels in this simulation summarize those different contracts.

## Hooks Step by Step

The default registers `check_release` at `PreToolUse` with the matcher `Edit|Write`. The files begin as `notes.txt = draft` and `release.txt = locked`. Here, `locked` is the file's text content, not an operating-system lock. Both proposed tools would succeed if invoked.

1. **c1 proposes an edit.** `Edit("notes.txt", "draft", "reviewed")` asks to replace one piece of text. Neither the file nor the tool count changes when the request arrives.
2. **The pre-event matches h1.** Both the event and the tool match the registration. The callback records c1 as an *attempt*, sees an unprotected target, and returns `no-decision`. A callback running does not mean it must veto the call.
3. **The harness executes c1.** The permissive baseline lets it proceed. Edit replaces `draft` with `reviewed`; the tool count becomes 1. The successful result then produces `PostToolUse`. h1 is registered at the other event, so no callback runs there.
4. **c2 proposes a protected write.** `Write("release.txt", "published")` reaches the pre-event. Write is covered by the matcher, so the callback runs, records a second attempt, and returns `deny`.
5. **The harness consumes the denial.** It closes the execution path and finalizes c2 as blocked. Write is never invoked. There is no successful result and no post-success event for c2.

The final state is `notes.txt = reviewed`, `release.txt = locked`, **2 hook invocations and 1 tool invocation**. Both callback records describe attempts; only the call ledger and the actual tool result establish that c1 executed. The unchanged protected file, next to the stopped request, is the default run's proof.

## When a Hook Runs Too Late or Misses a Tool

Change only the event to `PostToolUse`. Both calls now pass the pre-event without invoking h1, and the baseline permits both tools. c2 writes `published` before the callback receives it. The callback recognizes the protected target and reports the violation, but the final file remains `published`. The audit is useful evidence of a completed action; it did not prevent that action.

Now return to `PreToolUse` and narrow the matcher to `Edit`. c1 still reaches the callback. c2's pre-event still occurs, but Write fails the tool-name match. The callback never gets to inspect `release.txt`, so the call executes without a veto. Moving the hook earlier cannot repair missing coverage.

| Registration | Hook runs | Tool runs | Protected write |
|---|---:|---:|---|
| PreToolUse + Edit\|Write | 2 | 1 | Prevented; file stays locked |
| PreToolUse + Edit | 1 | 2 | Missed; file becomes published |
| PostToolUse + Edit\|Write | 2 | 2 | Reported after execution; file stays published |
| PostToolUse + Edit | 1 | 2 | Missed; file becomes published |

Each recipe pauses at c2's request, with c1 complete and `release.txt` still `locked`. Predict whether this registration will prevent the write, then press Play and follow the event, match, and file effect in order. Compare **Prevent the write** with **Check after execution** to change timing alone; compare it with **Miss the Write tool** to change coverage alone. The two files and requests are identical across runs.

Post hooks also support useful automation that belongs after success, such as logging a result or formatting an edited file. [Claude Code's automation guide](https://code.claude.com/docs/en/hooks-guide#auto-format-code-after-edits) gives a formatting example. Choose the event for the action you need, then check which tools can reach that event's handler.

## Edge Cases

- **Right tool, wrong event.** A Write-compatible matcher does not invoke a post-registered callback at a pre-event.
- **Right event, wrong tool.** The pre-event occurs for c2 even when `Edit` excludes Write. An absent callback record is different from a callback returning no-decision.
- **Matched, harmless target.** c1 invokes the default callback and proceeds. The tool matcher selects candidates; the callback's target check decides what to report.
- **Blocked before execution.** c2 produces neither a success result nor a post-success event. It is a rejected request, not a tool that ran and failed.
- **Both calls match after execution.** Full matching coverage provides a complete audit of these two successful fixture calls. It still cannot make the protected write unexecuted.

## Common Mistakes

- Treating an event firing as proof that a callback ran. Check the registration match and invocation count separately.
- Reading an attempt audit entry as a completion record. The default callback runs before the tool has any result.
- Treating `no-decision` as an allow rule. The baseline permission policy decides what happens next when the hook does not veto.
- Assuming a post-event warning restores previous file contents. Recovery needs a separate action; this callback only reports.
- Putting a filename into a tool-name matcher. In this harness, `Edit|Write` selects tools and the callback examines `release.txt` afterward.

## A Note on Simplification

This is a deterministic explainer with two authored requests, two in-memory files, and one synchronous callback. The matcher, callback, file edit, and write execute locally; no model output is recorded or invented. The fixture's baseline permission policy permits both tools, making the hook's effect measurable. Real runtimes add path handling, other execution routes, multiple callbacks, timeouts, asynchronous work, input rewriting, and other lifecycle events. Some hook types themselves use models. This lesson is not a production enforcement implementation or a substitute for formal training.
