Best on a larger screen — the animation and controls have more room to breathe.
Inputs

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

RegistrationHook runsTool runsProtected write
PreToolUse + Edit|Write21Prevented; file stays locked
PreToolUse + Edit12Missed; file becomes published
PostToolUse + Edit|Write22Reported after execution; file stays published
PostToolUse + Edit12Missed; 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 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.