Hooks have many lifecycle events - twenty-one in the documentation - and holding them in your head as a list is pointless. It is more useful to see the groups: session start and end and workspace opening, tool calls before and after, subagent start and stop, shell command execution and calls to external servers, file reads and edits, prompt submission and context compaction, the agent's responses and thoughts, and also editor suggestion events. Grouping answers not the question of what exists but the question of at which moment you can intervene.
The groups are needed not for elegance but for choosing the attachment point. A command check must stand before execution, not after; formatting after a file edit; an audit of the start of work at session start; control of what goes into a request before it is sent. The before and after pairs exist precisely for that reason: the before event gives the right to forbid, the after event only the right to react. A mistake in the choice yields a handler that fires correctly and is nevertheless useless, because the moment has already passed.
It helps to lay the event groups out in a table once. Below is that map. It also reminds you of an important limitation: cloud agents do not support the whole set, and user-level hooks do not exist there at all. So a policy built only at the user level simply does not apply in the cloud, and that must be taken into account when moving a process from a local machine to an autonomous run.
| Group | Events |
|---|---|
| Session | sessionStart, sessionEnd, workspaceOpen |
| Tools | preToolUse, postToolUse, postToolUseFailure |
| Subagents | subagentStart, subagentStop |
| Shell and external servers | before/afterShellExecution, before/afterMCPExecution |
| Files | beforeReadFile, afterFileEdit |
| Prompt and context | beforeSubmitPrompt, preCompact |
| Agent output | afterAgentResponse, afterAgentThought, stop |
| Editor suggestions | beforeTabFileRead, afterTabFileEdit |
Every handler has parameters worth setting deliberately. The command and its type, a timeout, a limit on repeated firings, the failure mode and a filter narrowing the area of application. The filter saves more than it seems: a handler attached to a frequent event without narrowing pays with its own time on every firing. The repeat limit protects against a loop where a handler's own action fires the same event again: the default value is small, and that is sensible - better to hit a limit than to spin forever.
Handlers come in two kinds, and the difference between them is the same as throughout this book. A command executes identically every time: it receives the event on input and returns a decision. A handler based on a request to the model is more flexible because it understands meaning, but its decision is probabilistic and may differ on identical input. For strict policy the command is chosen; the model-based handler belongs where an assessment rather than a prohibition is needed and where a mistake is not critical.
Here is what that looks like on a concrete check. A command that erases a directory or pushes changes to the main branch must not run, rather than be examined after the fact. The handler is attached to the event before shell command execution, it reads the line, compares it against its list and returns a refusal. What happens next is decided by the failure mode, and that is where people go wrong most often: by default a failure of the handler itself does not block the action, which means a crashed script silently lets through what it was supposed to stop. For the check the whole thing was built for that is wrong, and the mode is switched so that a handler failure means a prohibition.
Compatibility with hooks from other agent tools is a separate topic. Cursor can map some foreign events onto its own, but the mapping is incomplete. That is a trap when porting: only part of the foreign events map across, so a handler may simply never attach, and the defaults differ - the repeat limit is five for Cursor handlers and unlimited for foreign ones. The right path during migration is to check every event separately: what arrives on input, what is expected on output and whether blocking actually works.
Hooks have a limit of applicability, and it runs where the boundary between a rule and a mechanism runs. A rule changes the model's behavior while remaining a recommendation; a handler executes outside the model and therefore gives a prohibition. Where a mistake costs data or money a handler is needed; where the matter is style and preference a rule is enough. There is also a sign that you have overdone the handlers: work has noticeably slowed down and some firings run into the timeout. Every event with a command attached costs time, and on frequent events that price adds up.
The engineering conclusion is simple: hooks give determinism but require engineering. The attachment point is chosen by meaning, the timeout and repeat limit by common sense, the failure mode by the cost of a mistake, and compatibility is verified rather than assumed. A handler written in five minutes and never tested gets in the way more often than it protects.
The typical failures are predictable. Attaching to the wrong event and getting a check after the fact. Forgetting the repeat limit and creating a loop. Building policy at the user level and being surprised it does not apply in the cloud. And porting foreign handlers by event name without checking the format and the blocking behavior.