Follow Discord
Sweep 22 Oct 2026 · 21:04Z Build v2.1.309 528 read Stable v2.1.308 Latest v2.1.309 Next v2.1.309 Feeds RSS JSON llms.txt llms-full.txt Unofficial
One change · claude-code

React to events with a mod changedplugins/mods/events

Nearest release: v2.1.305, published under an hour after upstream edited the page. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.

Upstream edited this page at 20 Oct 2026 18:44 UTC, give or take a minute or two: the time comes from Anthropic’s own sitemap rather than from a commit. This site recorded the change at 22 Oct 2026 18:07 UTC.

Upstream edited
Recorded here
Lines+29added
Lines−29removed
From line 4 where the diff opens
First seen 1 Oct 2026 this site's first read of the page
Recorded edits14to this page, all time

The whole hunk

from line 4, old and new numbered
/
lines
from line 4
44 
55A hook is an event handler: a function that Claude Code runs when a named event happens. Claude Code fires an event at each point where it's about to act, such as when it runs a tool, submits a prompt, sends a request to the model, or starts or ends a session. Your hook runs before Claude Code acts, so it can observe the event, rewrite it, or answer it in Claude Code's place. You register a hook with [`on(eventName, handler)`](/docs/en/plugins/mods/reference#the-hook-function).
66 
7Build your [first mod](/docs/en/plugins/mods/create) before you start here. For every event and its exact fields, see the [reference](/docs/en/plugins/mods/reference#events) or read [the types for your build](/docs/en/plugins/mods/create#get-the-types-for-your-build).
7Build your [first mod](/docs/en/plugins/mods/create.md) before you start here. For every event and its exact fields, see the [reference](/docs/en/plugins/mods/reference.md#events) or read [the types for your build](/docs/en/plugins/mods/create.md#get-the-types-for-your-build).
88 
99## How a hook handles an event
1010 
11A hook sits between an event and what Claude Code would do about it, so it can observe the event, rewrite it, or answer it itself. It receives three arguments: the [mods API](/docs/en/plugins/mods/api) as `$`, the event as `e`, and the next handler as `next`. The handlers for an event form a middleware chain. `next(e)` calls the next handler, which is another mod's hook or, at the end of the chain, Claude Code's own behavior, and it resolves to the result. What your hook does with `next` decides which of the three it does.
11A hook sits between an event and what Claude Code would do about it, so it can observe the event, rewrite it, or answer it itself. It receives three arguments: the [mods API](/docs/en/plugins/mods/api.md) as `$`, the event as `e`, and the next handler as `next`. The handlers for an event form a middleware chain. `next(e)` calls the next handler, which is another mod's hook or, at the end of the chain, Claude Code's own behavior, and it resolves to the result. What your hook does with `next` decides which of the three it does.
1212 
1313### Observe an event
1414 
from line 64
6464})
6565```
6666 
67When Claude tries a Bash command, the command doesn't run, and Claude reads the `deny` text as the tool's result. Each event has its own result shape, which the [events reference](/docs/en/plugins/mods/reference#events) lists.
67When Claude tries a Bash command, the command doesn't run, and Claude reads the `deny` text as the tool's result. Each event has its own result shape, which the [events reference](/docs/en/plugins/mods/reference.md#events) lists.
6868 
6969### Filter which events a hook handles
7070 
from line 83
8383 
8484`hook` runs once for a Bash, Edit, or Write call, and once for a call to a tool whose name starts with `mcp__github__`. A call to any other tool, such as Read, matches none of the three, so `hook` doesn't run for it.
8585 
86The event name can be a wildcard. `'classic.*'` matches every [settings hook event](#hook-the-settings-hook-events). `'*'` matches every event except the [telemetry events](/docs/en/plugins/mods/reference#telemetry), which take their own name and a `{ to: 'collector' }` filter.
86The event name can be a wildcard. `'classic.*'` matches every [settings hook event](#hook-the-settings-hook-events). `'*'` matches every event except the [telemetry events](/docs/en/plugins/mods/reference.md#telemetry), which take their own name and a `{ to: 'collector' }` filter.
8787 
8888Register each event once per matcher. If you call `on` twice for `session.start` with no matcher, the module fails to load with `on("session.start") is registered twice without a matcher`. Put everything your mod does at session start in one hook.
8989 
9090## Hook what Claude is doing
9191 
92Handle these events to see or change a tool call, a prompt, or a turn as it happens. For every event and what a hook can return, see the [events reference](/docs/en/plugins/mods/reference#events).
92Handle these events to see or change a tool call, a prompt, or a turn as it happens. For every event and what a hook can return, see the [events reference](/docs/en/plugins/mods/reference.md#events).
9393 
9494### Guard or change a tool call
9595 
from line 131
131131 
132132* **Change the call**: pass changed arguments to `next`.
133133* **Retry the call**: call `next(e)` again. A hook that sees `isError` on the first result can run the tool a second time and return that result.
134* **Answer the call yourself**: return an object with a `result` field, without calling `next`, and for a built-in tool, give `result` the shape that tool's own result has in [the types for your build](/docs/en/plugins/mods/create#get-the-types-for-your-build). No permission prompt appears and the tool doesn't run, so the result you return is all Claude learns about what happened.
134* **Answer the call yourself**: return an object with a `result` field, without calling `next`, and for a built-in tool, give `result` the shape that tool's own result has in [the types for your build](/docs/en/plugins/mods/create.md#get-the-types-for-your-build). No permission prompt appears and the tool doesn't run, so the result you return is all Claude learns about what happened.
135135* **Withhold the result from Claude**: return `{ deny: reason }` after `await next(e)`. Claude reads your reason in place of what `next` returned. When the tool ran, the deny keeps its result from Claude and undoes nothing the tool did. When the tool ran and succeeded, the reason follows a note such as `Bash ran, and a plugin withheld its result:`.
136136 
137Hooks in your organization's [managed settings](/docs/en/server-managed-settings) run before any mod's `tool.call` hook. When a managed hook blocks the call, no mod's `tool.call` hook runs for that call.
137Hooks in your organization's [managed settings](/docs/en/server-managed-settings.md) run before any mod's `tool.call` hook. When a managed hook blocks the call, no mod's `tool.call` hook runs for that call.
138138 
139139#### Hold a tool call until the user decides
140140 
from line 173
173173* **The user types an answer**: `$.ui.ask` resolves to the typed text. The hook compares it with `Run it`, so any other text refuses the command.
174174* **Nobody answers**: `$.ui.ask` rejects when the user dismisses the question or picks **Chat about this**, and in a `claude -p` run, so the `catch` block leaves the answer at `Refuse`
175175 
176Keep the wait inside a mods API call such as `$.ui.ask`, because that time doesn't count against the hook's [time limit](/docs/en/plugins/mods/reference#limits). Time spent awaiting a promise of your own does count. Claude Code skips a hook that times out, so the held command would run.
176Keep the wait inside a mods API call such as `$.ui.ask`, because that time doesn't count against the hook's [time limit](/docs/en/plugins/mods/reference.md#limits). Time spent awaiting a promise of your own does count. Claude Code skips a hook that times out, so the held command would run.
177177 
178178#### Approve or refuse a tool call before the user is asked
179179 
180180To decide whether a tool call may run, handle [`tool.check`](/docs/en/plugins/mods/reference#tools), the event where Claude Code makes that decision.
181181 
182For a fixed command or path, use a [permission rule](/docs/en/permissions#permission-rule-syntax) such as `Bash(npm test)`, which takes no code. Handle `tool.check` when the decision depends on what's true at that moment, such as the current Git branch or a value another hook recorded.
182For a fixed command or path, use a [permission rule](/docs/en/permissions.md#permission-rule-syntax) such as `Bash(npm test)`, which takes no code. Handle `tool.check` when the decision depends on what's true at that moment, such as the current Git branch or a value another hook recorded.
183183 
184184* **What to return**: to decide the call yourself, return an object of your own, such as `{ decision: 'allow' }` or `{ decision: 'deny', reason }`. To leave the decision alone, return what `await next(e)` gives you.
185185* **What `await next(e)` gives you**: Claude Code's own decision, as an object whose `decision` is `allow`, `ask`, or `deny`. No permission prompt appears while `next(e)` runs.
186186* **Arguments**: `e.input` holds the tool's arguments, such as `command` for Bash
187* **When `tool.check` fires**: after the [`PreToolUse` hooks](/docs/en/hooks#pretooluse) run, and before the permission prompt, [auto mode](/docs/en/permission-modes#eliminate-prompts-with-auto-mode)'s classifier, or a [`PermissionRequest`](/docs/en/hooks#permissionrequest) hook
187* **When `tool.check` fires**: after the [`PreToolUse` hooks](/docs/en/hooks#pretooluse) run, and before the permission prompt, [auto mode](/docs/en/permission-modes.md#eliminate-prompts-with-auto-mode)'s classifier, or a [`PermissionRequest`](/docs/en/hooks#permissionrequest) hook
188188 
189189This hook refuses `git push` while the current branch is `main`:
190190 
from line 208
208208 
209209> [!NOTE]
210210>
211> A `tool.check` hook can also return `allow` or `ask`. In [plan mode](/docs/en/permissions#hook-approvals-in-plan-mode), for a call that isn't read-only, such as a file edit, your hook can tighten the decision `next(e)` returns but can't loosen it. For the permission rules and `PreToolUse` hook decisions your hook's `allow` can't replace, see [Extend permissions with hooks](/docs/en/permissions#precedence-among-rules-hooks-and-mods).
211> A `tool.check` hook can also return `allow` or `ask`. In [plan mode](/docs/en/permissions.md#hook-approvals-in-plan-mode), for a call that isn't read-only, such as a file edit, your hook can tighten the decision `next(e)` returns but can't loosen it. For the permission rules and `PreToolUse` hook decisions your hook's `allow` can't replace, see [Extend permissions with hooks](/docs/en/permissions.md#precedence-among-rules-hooks-and-mods).
212212 
213213#### Check whether a tool call is read-only
214214 
from line 216
216216 
217217Some tools, such as Read, Grep, Glob, and WebSearch, report every call as read-only. Bash and MCP tools report a call as read-only in these cases:
218218 
219* **Bash**: the command passes Claude Code's built-in [read-only command check](/docs/en/permissions#read-only-commands)
219* **Bash**: the command passes Claude Code's built-in [read-only command check](/docs/en/permissions.md#read-only-commands)
220220* **An MCP tool**: the server sets the tool's `readOnlyHint` annotation to `true`
221221 
222The value is the tool's own report, so don't approve a call on this field alone. For example, WebFetch and Agent calls report as read-only, though [WebFetch](/docs/en/tools-reference#webfetch-tool-behavior) requests a URL and [Agent](/docs/en/tools-reference#agent-tool-behavior) starts a subagent that runs tools of its own.
222The value is the tool's own report, so don't approve a call on this field alone. For example, WebFetch and Agent calls report as read-only, though [WebFetch](/docs/en/tools-reference.md#webfetch-tool-behavior) requests a URL and [Agent](/docs/en/tools-reference.md#agent-tool-behavior) starts a subagent that runs tools of its own.
223223 
224224This hook returns an `ask` for every `mcp__github__` call that isn't read-only and wasn't already denied:
225225 
from line 245
245245 
246246| To do this | Return this |
247247| :- | :- |
248| Rewrite the prompt. The transcript and your [prompt history](/docs/en/interactive-mode#command-history) show the new text. | `next({ ...e, text: newText })` |
248| Rewrite the prompt. The transcript and your [prompt history](/docs/en/interactive-mode.md#command-history) show the new text. | `next({ ...e, text: newText })` |
249249| Add text only Claude reads, after the prompt | `next({ ...e, context: [...(e.context ?? []), extraText] })` |
250250| Stop the prompt from being sent | `{ drop: 'the reason' }` |
251251 
from line 267
267267 
268268To stop a prompt, return `{ drop: 'the reason' }` without calling `next`. The text goes back into the user's prompt input, and they see `Prompt dropped by a hook:` followed by your reason, so address the reason to them. If your hook returns a `drop` after its `next(e)` call let the prompt through, the turn still runs, and the hook [fails](#handle-a-hook-that-fails) with a message that includes `a drop after its next() was answered`.
269269 
270[Other events](/docs/en/plugins/mods/reference#prompts-and-what-claude-reads) cover the rest of what Claude reads: `prompt.section` for each section of the system prompt, `prompt.context` for the context sent with the first message, and `skill.prompt` for a skill's text. Text from these hooks that changes between requests [invalidates the prompt cache](/docs/en/prompt-caching). In a `skill.prompt` hook, you can also read [which plugin or file the skill came from](/docs/en/plugins/mods/reference#skill-prompt-fields).
270[Other events](/docs/en/plugins/mods/reference.md#prompts-and-what-claude-reads) cover the rest of what Claude reads: `prompt.section` for each section of the system prompt, `prompt.context` for the context sent with the first message, and `skill.prompt` for a skill's text. Text from these hooks that changes between requests [invalidates the prompt cache](/docs/en/prompt-caching.md). In a `skill.prompt` hook, you can also read [which plugin or file the skill came from](/docs/en/plugins/mods/reference.md#skill-prompt-fields).
271271 
272272### Follow a turn
273273 
from line 279
279279| `turn.step` | Claude Code is about to send one request to the model. A turn with tool calls has several. `e.agentId` is set for a subagent's request. | Read each request's token usage, send it to a different model with `next({ ...e, model })`, or answer without calling the model |
280280| `turn.complete` | The turn ended, including a turn the user interrupted, where `e.isAborted` is `true`. `e.answer` is Claude's final text, `e.durationMs` how long it took, and `e.usage` the turn's token totals. A subagent's turn fires it with `e.agentId` set. | Observe, or return an object with a `text` field, such as `{ text: 'Done in 12 seconds' }`, to show a line under the answer |
281281 
282Write a `turn.step` hook as an async generator, because the event streams. `yield* next(e)` forwards the response as it streams and evaluates to the finished result. This hook logs how much of each request the Claude API served from the [prompt cache](/docs/en/prompt-caching):
282Write a `turn.step` hook as an async generator, because the event streams. `yield* next(e)` forwards the response as it streams and evaluates to the finished result. This hook logs how much of each request the Claude API served from the [prompt cache](/docs/en/prompt-caching.md):
283283 
284284```javascript theme={null}
285285// function* makes the hook a generator, which can pass the response on piece by piece
from line 299
299299 
300300`result.usage` holds the token counts the Claude API reports for a request, plus the `model` that answered: `input_tokens`, `output_tokens`, `cache_read_input_tokens`, and `cache_creation_input_tokens`. The hook runs for subagents' requests too, so check `e.agentId` when you want only the main conversation.
301301 
302To see the tool calls that the API ran itself during the request, such as calls to the [advisor tool](/docs/en/advisor), read `result.serverToolUses`. Claude Code doesn't run these calls, so no `tool.call` or `tool.check` hook fires for them. The field is absent when the response has no such calls, and it requires Claude Code v2.1.290 or later.
302To see the tool calls that the API ran itself during the request, such as calls to the [advisor tool](/docs/en/advisor.md), read `result.serverToolUses`. Claude Code doesn't run these calls, so no `tool.call` or `tool.check` hook fires for them. The field is absent when the response has no such calls, and it requires Claude Code v2.1.290 or later.
303303 
304304### Handle the settings hook events
305305 
306Settings hooks are the command, HTTP, prompt, and agent hooks you configure in settings files. Each [settings hook event](/docs/en/hooks#hook-events), such as `Stop`, `SessionEnd`, or `PostToolUse`, is also an event named `classic.` followed by the settings hook event's name, such as `classic.Stop`. `e` is the JSON a settings hook receives on stdin, including `transcript_path`.
306Settings hooks are the command, HTTP, prompt, and agent hooks you configure in settings files. Each [settings hook event](/docs/en/hooks.md#hook-events), such as `Stop`, `SessionEnd`, or `PostToolUse`, is also an event named `classic.` followed by the settings hook event's name, such as `classic.Stop`. `e` is the JSON a settings hook receives on stdin, including `transcript_path`.
307307 
308308This hook uses `Stop`, which fires when Claude finishes responding, to log where the session's transcript is saved:
309309 
from line 328
328328 
329329Claude Code orders the chain by where each mod comes from:
330330 
3311. The built-in guard `sec-default@builtin`, a mod built into Claude Code that `/plugin` lists as `cc-plugin-sec-default`, where [it loads](/docs/en/plugins/mods/admin#know-what-happens-by-default), mods your organization lists in [`prependPlugins`](/docs/en/plugins/mods/admin#install-your-organizations-mods), and then any other mod that counts as your organization's and isn't in `appendPlugins`
3311. The built-in guard `sec-default@builtin`, a mod built into Claude Code that `/plugin` lists as `cc-plugin-sec-default`, where [it loads](/docs/en/plugins/mods/admin.md#know-what-happens-by-default), mods your organization lists in [`prependPlugins`](/docs/en/plugins/mods/admin#install-your-organizations-mods), and then any other mod that counts as your organization's and isn't in `appendPlugins`
3323322. Mods you install
3333333. Mods your organization lists in `appendPlugins`
3343344. Other mods built into Claude Code
from line 342
342342* **`PreToolUse` hooks from managed settings**: run before the first mod's `tool.call` hook. When a managed hook blocks the call, no mod's `tool.call` hook runs for that call.
343343* **`PreToolUse` hooks from every other settings file and from plugins' `hooks/hooks.json`**: run after the last mod calls `next`, as part of Claude Code's own behavior. A mod that answers `tool.call` without calling `next` keeps them from running, and a mod that calls `next` sees their decision in the result it returns.
344344 
345[`tool.check`](#approve-or-refuse-a-tool-call-before-the-user-is-asked) fires after the `PreToolUse` hooks have run. For the permission rules and `PreToolUse` hook decisions a `tool.check` hook's `allow` can't replace, see [Extend permissions with hooks](/docs/en/permissions#precedence-among-rules-hooks-and-mods).
345[`tool.check`](#approve-or-refuse-a-tool-call-before-the-user-is-asked) fires after the `PreToolUse` hooks have run. For the permission rules and `PreToolUse` hook decisions a `tool.check` hook's `allow` can't replace, see [Extend permissions with hooks](/docs/en/permissions.md#precedence-among-rules-hooks-and-mods).
346346 
347347### Handle a hook that fails
348348 
from line 351
351351* **It failed before calling `next`**: Claude Code skips it, and the next handler runs in its place
352352* **It failed after `next` resolved**: that result stands, and nothing runs a second time
353353 
354One line names the mod, the event, and the reason, such as `my-mod: tool.call hook skipped: threw Error: boom`. Where you read it depends on the session, as [Find out why a mod does nothing](/docs/en/plugins/mods/troubleshoot#find-out-why-a-mod-does-nothing) lists. A `ui.render` hook whose drawing doesn't validate is reported differently, as [Build a tree from elements](/docs/en/plugins/mods/interface#build-a-tree-from-elements) describes.
354One line names the mod, the event, and the reason, such as `my-mod: tool.call hook skipped: threw Error: boom`. Where you read it depends on the session, as [Find out why a mod does nothing](/docs/en/plugins/mods/troubleshoot.md#find-out-why-a-mod-does-nothing) lists. A `ui.render` hook whose drawing doesn't validate is reported differently, as [Build a tree from elements](/docs/en/plugins/mods/interface.md#build-a-tree-from-elements) describes.
355355 
356356To make a hook that blocks calls fail closed, add a `.catch` error handler that answers in its place. Here, `guard` is your hook function, and the handler tests [`next.called`](/docs/en/plugins/mods/reference#the-hook-function) to tell whether `guard` had already called `next` when it failed:
357357 
from line 370
370370* **`guard` failed before it called `next`**: the command doesn't run, and Claude reads the `deny` text with the kind at the end
371371* **`guard` failed after it called `next`**: the handler's `next(e)` resolves to the result that `guard`'s call produced without running the command again, and Claude reads that result
372372 
373The handler has a shorter [time limit](/docs/en/plugins/mods/reference#limits) of its own. If the handler itself throws or times out, Claude Code skips the hook as if it had no handler. When `guard` hadn't called `next`, the command then goes on as it would without the mod.
373The handler has a shorter [time limit](/docs/en/plugins/mods/reference.md#limits) of its own. If the handler itself throws or times out, Claude Code skips the hook as if it had no handler. When `guard` hadn't called `next`, the command then goes on as it would without the mod.
374374 
375The same handler shape fits a guard on `prompt.submit` or `config.set`. When `next.called` is false, return the refusal that the [events reference](/docs/en/plugins/mods/reference#events) lists for that event: `{ drop: 'the reason' }` for `prompt.submit`, `{ deny: 'the reason' }` for `config.set`.
375The same handler shape fits a guard on `prompt.submit` or `config.set`. When `next.called` is false, return the refusal that the [events reference](/docs/en/plugins/mods/reference.md#events) lists for that event: `{ drop: 'the reason' }` for `prompt.submit`, `{ deny: 'the reason' }` for `config.set`.
376376 
377377At `tool.check` and `plugin.register`, a refusal returned after `next` resolved still holds, so return it without testing `next.called`:
378378 
379379* **`tool.check`**: return `{ decision: 'deny', reason: 'the reason' }`
380* **`plugin.register`**: return `{ refuse: 'the reason' }`, as [Refuse mods when your check fails](/docs/en/plugins/mods/admin#refuse-mods-when-your-check-fails) shows
380* **`plugin.register`**: return `{ refuse: 'the reason' }`, as [Refuse mods when your check fails](/docs/en/plugins/mods/admin.md#refuse-mods-when-your-check-fails) shows
381381 
382382At `tool.call`, a `deny` returned after `next` resolved [withholds the result from Claude](#guard-or-change-a-tool-call).
383383 
384384## Next steps
385385 
386* [Use the mods API](/docs/en/plugins/mods/api): add commands and tools, call a model, and run work on a timer
387* [Draw in the interface](/docs/en/plugins/mods/interface): show what your hooks collect in a pane or above the prompt
388* [Test a mod](/docs/en/plugins/mods/test): fire any of these events from a test
389* [Mods reference](/docs/en/plugins/mods/reference): every event, every mods API method, and the limits
386* [Use the mods API](/docs/en/plugins/mods/api.md): add commands and tools, call a model, and run work on a timer
387* [Draw in the interface](/docs/en/plugins/mods/interface.md): show what your hooks collect in a pane or above the prompt
388* [Test a mod](/docs/en/plugins/mods/test.md): fire any of these events from a test
389* [Mods reference](/docs/en/plugins/mods/reference.md): every event, every mods API method, and the limits
390390 
Feedback