Use the mods API changedplugins/mods/api
Nearest release: v2.1.306, 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 21 Oct 2026 02:10 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 23 Oct 2026 19:07 UTC.
Upstream edited
Recorded here
Lines+43added
Lines−43removed
From line
2
where the diff opens
First seen
1 Oct 2026
this site's first read of the page
Recorded edits15to this page, all time
The whole hunk
from line 2, old and new numbered
/
from line 2
22
33> Call the mods API from a Claude Code mod to add commands and tools, call a model, run work on a timer, message other sessions, and reach files and the network.
44
5The mods API is the set of methods a mod calls to act: add commands and tools, call a model, run work between events, and reach the file system, processes, and the network. Every hook receives it as its first argument, `$`, with the methods grouped in namespaces such as `$.ui` and `$.fs`. [Events](/docs/en/plugins/mods/events.md) decide when a hook runs, and the mods API is what the hook calls once it does.
5The mods API is the set of methods a mod calls to act: add commands and tools, call a model, run work between events, and reach the file system, processes, and the network. Every hook receives it as its first argument, `$`, with the methods grouped in namespaces such as `$.ui` and `$.fs`. [Events](https://code.claude.com/docs/en/plugins/mods/events.md) decide when a hook runs, and the mods API is what the hook calls once it does.
66
7Build your [first mod](/docs/en/plugins/mods/create.md) before you start here. For every method, see [mods API methods](/docs/en/plugins/mods/reference.md#mods-api-methods) or read [the types for your build](/docs/en/plugins/mods/create.md#get-the-types-for-your-build).
7Build your [first mod](https://code.claude.com/docs/en/plugins/mods/create.md) before you start here. For every method, see [mods API methods](https://code.claude.com/docs/en/plugins/mods/reference.md#mods-api-methods) or read [the types for your build](https://code.claude.com/docs/en/plugins/mods/create.md#get-the-types-for-your-build).
88
99## Add a command or a tool
1010
11A mod can add a command for the user to run and a tool for Claude to call. Register both in a [`session.start`](/docs/en/plugins/mods/reference#session) hook. Claude Code waits for that hook before the first prompt, so what you register is available from the first turn.
11A mod can add a command for the user to run and a tool for Claude to call. Register both in a [`session.start`](https://code.claude.com/docs/en/plugins/mods/reference#session) hook. Claude Code waits for that hook before the first prompt, so what you register is available from the first turn.
1212
1313### Add a command
1414
15A command is for the user. Register it, then handle [`command.run`](/docs/en/plugins/mods/reference#commands-and-configuration) for its name. This example adds a `/standup` command that takes an optional number of days:
15A command is for the user. Register it, then handle [`command.run`](https://code.claude.com/docs/en/plugins/mods/reference#commands-and-configuration) for its name. This example adds a `/standup` command that takes an optional number of days:
1616
1717```javascript theme={null}
1818on('session.start', async ($, e, next) => {
from line 30
3030
3131After the session starts, `/standup` appears with its description in the list you see when you type `/`. The `argumentHint` shows in the prompt after you type the command and a space, as in `/standup [days]`. When you run `/standup 3`, the second hook returns `Summary for the last 3 day(s): ...`, and the transcript shows that text after the plugin's name. The hook never calls `next`, because the command has no behavior other than yours.
3232
33The `text` you return prints in the transcript and Claude reads it. To print nothing, as a command that only opens a [pane](/docs/en/plugins/mods/interface.md#pick-where-to-draw) does, return `{}`. To let the command run while Claude is working, add `immediate: true` to the registration.
33The `text` you return prints in the transcript and Claude reads it. To print nothing, as a command that only opens a [pane](https://code.claude.com/docs/en/plugins/mods/interface.md#pick-where-to-draw) does, return `{}`. To let the command run while Claude is working, add `immediate: true` to the registration.
3434
3535Pick a name that no built-in command uses. Type `/` in a session to see them. `$.command.register` throws for a taken name, with a message such as `"/focus" refused: it is the built-in /focus`. A hook that throws is skipped, so the rest of your `session.start` hook doesn't run either. Register commands last in that hook, or wrap the call in `try` and `catch`.
3636
3737### Add a tool
3838
39A tool is for Claude. Register it with a name, a description Claude reads, and a JSON Schema for its input. Claude sees it under a longer name made of `mcp__`, your plugin's name, two underscores, and the name you registered. You handle its calls in a [`tool.call`](/docs/en/plugins/mods/events#guard-or-change-a-tool-call) hook filtered to that full name. This example, from a plugin named `my-mod`, registers `ticket`, so the full name is `mcp__my-mod__ticket`. It gives Claude a tool that looks up a ticket in an issue tracker:
39A tool is for Claude. Register it with a name, a description Claude reads, and a JSON Schema for its input. Claude sees it under a longer name made of `mcp__`, your plugin's name, two underscores, and the name you registered. You handle its calls in a [`tool.call`](https://code.claude.com/docs/en/plugins/mods/events#guard-or-change-a-tool-call) hook filtered to that full name. This example, from a plugin named `my-mod`, registers `ticket`, so the full name is `mcp__my-mod__ticket`. It gives Claude a tool that looks up a ticket in an issue tracker:
4040
4141```javascript theme={null}
4242on('session.start', async ($, e, next) => {
from line 63
6363
6464> [!TIP]
6565>
66> When [MCP tool search](/docs/en/mcp.md#scale-with-mcp-tool-search) defers a registered tool, Claude sees its name but not its description until it searches for it. If Claude should consider the tool on every turn, add [`isDeferred: false`](/docs/en/plugins/mods/reference#tools) to the registration to [load the full tool upfront](/docs/en/mcp.md#exempt-a-server-from-deferral). The field requires Claude Code v2.1.293 or later, and earlier versions ignore it.
66> When [MCP tool search](https://code.claude.com/docs/en/mcp.md#scale-with-mcp-tool-search) defers a registered tool, Claude sees its name but not its description until it searches for it. If Claude should consider the tool on every turn, add [`isDeferred: false`](https://code.claude.com/docs/en/plugins/mods/reference#tools) to the registration to [load the full tool upfront](https://code.claude.com/docs/en/mcp.md#exempt-a-server-from-deferral). The field requires Claude Code v2.1.293 or later, and earlier versions ignore it.
6767
6868## Call a model
6969
from line 74
7474| In the request | `$.model.complete` | `$.model.fork` |
7575| :- | :- | :- |
7676| Model | The `model` you pass | The session's model |
77| System prompt | A short [attribution block](/docs/en/llm-gateway-protocol.md#system-prompt-attribution-block), then your `system` if you pass one | The session's system prompt |
77| System prompt | A short [attribution block](https://code.claude.com/docs/en/llm-gateway-protocol.md#system-prompt-attribution-block), then your `system` if you pass one | The session's system prompt |
7878| Messages | One user message, your `prompt` | The conversation so far, then your `prompt` as a user message |
7979| CLAUDE.md and other project context | Not included | Included, as in the conversation's last request |
8080| Tools | None | Claude's tools, which the model can't call |
8181
82A fork repeats the conversation's last request, so the Claude API serves most of it from the [prompt cache](/docs/en/prompt-caching.md) while the conversation is still cached.
82A fork repeats the conversation's last request, so the Claude API serves most of it from the [prompt cache](https://code.claude.com/docs/en/prompt-caching.md) while the conversation is still cached.
8383
84Both calls use the session's credentials, so they bill to the user's plan, API key, or cloud provider. [The types for your build](/docs/en/plugins/mods/create.md#get-the-types-for-your-build) document every `$.model` method.
84Both calls use the session's credentials, so they bill to the user's plan, API key, or cloud provider. [The types for your build](https://code.claude.com/docs/en/plugins/mods/create.md#get-the-types-for-your-build) document every `$.model` method.
8585
8686### Send one prompt
8787
from line 110
110110
111111A Claude API failure doesn't reject the call, so check `r.isAnswered`, and read `r.reason` when it's `false`. The call rejects for a request Claude Code won't send, such as a model your organization blocks.
112112
113[The types for your build](/docs/en/plugins/mods/create.md#get-the-types-for-your-build) list the other options, such as `effort`, and the [limits](/docs/en/plugins/mods/reference.md#limits) give the `maxTokens` default.
113[The types for your build](https://code.claude.com/docs/en/plugins/mods/create.md#get-the-types-for-your-build) list the other options, such as `effort`, and the [limits](https://code.claude.com/docs/en/plugins/mods/reference.md#limits) give the `maxTokens` default.
114114
115115### Use prompt caching
116116
from line 141
141141
142142The TTL and the number of breakpoints have these limits:
143143
144* **TTL**: a cache entry lasts five minutes after its last use. The TTL comes from the user's Claude Code settings, not from the call. For one hour, set [`subagentPromptCacheTtl`](/docs/en/prompt-caching#choose-the-ttl-yourself) to `1h`.
144* **TTL**: a cache entry lasts five minutes after its last use. The TTL comes from the user's Claude Code settings, not from the call. For one hour, set [`subagentPromptCacheTtl`](https://code.claude.com/docs/en/prompt-caching#choose-the-ttl-yourself) to `1h`.
145145* **Breakpoints per request**: the API accepts [up to four](https://platform.claude.com/docs/en/build-with-claude/prompt-caching#when-to-use-multiple-breakpoints), and one more comes back as an `api-error` in `r.reason`
146146
147147#### Choose between `prompt` and `system`
from line 149
149149Put the static content that your calls share at the start of `prompt` unless you know that your requests go directly to the Claude API:
150150
151151* **Directly to the Claude API, with an API key or a Claude subscription**: either field works
152* **Through [Amazon Bedrock](/docs/en/amazon-bedrock.md), [Claude Platform on AWS](/docs/en/claude-platform-on-aws.md), [Google Cloud's Agent Platform](/docs/en/google-vertex-ai.md), [Microsoft Foundry](/docs/en/microsoft-foundry.md), or an [LLM gateway](/docs/en/llm-gateway)**: use `prompt`. Claude Code begins the system prompt with an [attribution block](/docs/en/llm-gateway-protocol.md#system-prompt-attribution-block) whose fingerprint comes from the start of the user message. The `api.anthropic.com` endpoint strips that block before caching. Other endpoints receive it as part of the prompt, so a breakpoint in `system` can miss when `prompt` starts differently.
152* **Through [Amazon Bedrock](https://code.claude.com/docs/en/amazon-bedrock.md), [Claude Platform on AWS](https://code.claude.com/docs/en/claude-platform-on-aws.md), [Google Cloud's Agent Platform](https://code.claude.com/docs/en/google-vertex-ai.md), [Microsoft Foundry](https://code.claude.com/docs/en/microsoft-foundry.md), or an [LLM gateway](https://code.claude.com/docs/en/llm-gateway)**: use `prompt`. Claude Code begins the system prompt with an [attribution block](https://code.claude.com/docs/en/llm-gateway-protocol.md#system-prompt-attribution-block) whose fingerprint comes from the start of the user message. The `api.anthropic.com` endpoint strips that block before caching. Other endpoints receive it as part of the prompt, so a breakpoint in `system` can miss when `prompt` starts differently.
153153* **In a mod that other people run**: use `prompt`, because you don't choose their provider
154154
155155`system` comes before `prompt` in the prefix, so a breakpoint in `prompt` covers `system` too, and a call with a different `system` misses the cache.
from line 163
163163If both fields stay at zero on calls the model answered, nothing was cached. Check for each of these causes:
164164
165165* **The prefix is too short**: the API doesn't cache a prefix under the model's [minimum length](https://platform.claude.com/docs/en/build-with-claude/prompt-caching#cache-limitations), and it returns no error
166* **Prompt caching is disabled**: when a [`DISABLE_PROMPT_CACHING` variable](/docs/en/prompt-caching#disable-prompt-caching) applies to the model, Claude Code removes the breakpoints and sends the text uncached
167* **Your gateway strips `cache_control`**: a gateway can [remove the field and still return success](/docs/en/prompt-caching.md#where-the-cache-lives)
166* **Prompt caching is disabled**: when a [`DISABLE_PROMPT_CACHING` variable](https://code.claude.com/docs/en/prompt-caching#disable-prompt-caching) applies to the model, Claude Code removes the breakpoints and sends the text uncached
167* **Your gateway strips `cache_control`**: a gateway can [remove the field and still return success](https://code.claude.com/docs/en/prompt-caching.md#where-the-cache-lives)
168168* **Another mod rewrites the start of the text**: Claude Code then [sends it without breakpoints](#what-a-model-complete-hook-receives)
169169
170170### What a `model.complete` hook receives
171171
172If you hook the [`model.complete`](/docs/en/plugins/mods/reference#mods-api-calls) event to inspect or change other mods' requests, read the text from these fields:
172If you hook the [`model.complete`](https://code.claude.com/docs/en/plugins/mods/reference#mods-api-calls) event to inspect or change other mods' requests, read the text from these fields:
173173
174174* **`e.prompt`**: always a string. When the caller passed an array, it's the blocks' text concatenated in order.
175175* **`e.system`**: a string built the same way, or absent when the caller passed no `system`
from line 179
179179
180180## Run work in the background
181181
182Work that outlives one event, such as checking on something once a minute, runs on a timer you start from `session.start`. A hook itself runs for one event and has a [time limit](/docs/en/plugins/mods/reference.md#limits) on its own execution time. Time spent waiting on `next` or on a mods API call doesn't count, except a `$.clock.sleep`. `$.clock.every` and `$.clock.after` take the place of `setInterval` and `setTimeout`, with the delay in milliseconds first: `$.clock.after(5000, fn)` calls `fn` once, five seconds from now. Each returns a timer with a `cancel()` method, and `await $.clock.now()` gives the time in milliseconds.
182Work that outlives one event, such as checking on something once a minute, runs on a timer you start from `session.start`. A hook itself runs for one event and has a [time limit](https://code.claude.com/docs/en/plugins/mods/reference.md#limits) on its own execution time. Time spent waiting on `next` or on a mods API call doesn't count, except a `$.clock.sleep`. `$.clock.every` and `$.clock.after` take the place of `setInterval` and `setTimeout`, with the delay in milliseconds first: `$.clock.after(5000, fn)` calls `fn` once, five seconds from now. Each returns a timer with a `cancel()` method, and `await $.clock.now()` gives the time in milliseconds.
183183
184184This hook looks up a pull request's checks once a minute and shows the result under the prompt. `summarize` is a function of your own that turns the command's JSON output into a few words:
185185
from line 196
196196})
197197```
198198
199The session starts as usual. A minute later, a line appears under the prompt with a `⚠`, the mod's name, and then `checks:` and your summary. It's replaced once a minute after that. The timer's callback runs outside any event, so it keeps running between turns and doesn't start one. If the callback throws, the error goes to the [debug log](/docs/en/plugins/mods/troubleshoot.md#read-the-debug-log) and the timer runs again at the next interval.
199The session starts as usual. A minute later, a line appears under the prompt with a `⚠`, the mod's name, and then `checks:` and your summary. It's replaced once a minute after that. The timer's callback runs outside any event, so it keeps running between turns and doesn't start one. If the callback throws, the error goes to the [debug log](https://code.claude.com/docs/en/plugins/mods/troubleshoot.md#read-the-debug-log) and the timer runs again at the next interval.
200200
201201### Show something without starting a turn
202202
from line 205
205205| Call | What the user sees |
206206| :- | :- |
207207| `$.ui.status(text)` | One line under the prompt that stays until you change it. It starts with `⚠` and the mod's name, as in `⚠ my-mod: checks: 3 passing`. |
208| `$.ui.toast(text)` | A toast notification with the mod's name that disappears after a few seconds. It's a box at the top right in [fullscreen rendering](/docs/en/fullscreen.md), and one line at the right under the prompt in the classic renderer. |
208| `$.ui.toast(text)` | A toast notification with the mod's name that disappears after a few seconds. It's a box at the top right in [fullscreen rendering](https://code.claude.com/docs/en/fullscreen.md), and one line at the right under the prompt in the classic renderer. |
209209| `$.ui.log(text)` | A dim line in the transcript that Claude doesn't read. It starts with `●` and the mod's name, as in `● my-mod: build finished`. |
210| `$.ui.notify(text)` | A notification from their terminal, outside Claude Code, sent the way their [`preferredNotifChannel`](/docs/en/settings-reference#preferrednotifchannel) setting says. The mod's name is its title unless you pass a non-empty `{ title }`, which is shown as you give it. |
210| `$.ui.notify(text)` | A notification from their terminal, outside Claude Code, sent the way their [`preferredNotifChannel`](https://code.claude.com/docs/en/settings-reference#preferrednotifchannel) setting says. The mod's name is its title unless you pass a non-empty `{ title }`, which is shown as you give it. |
211211
212212`$.ui.notify` is the only one of these that answers. It resolves to `{ isSent: true, channel }` when the user's channel wrote the notification to their terminal. `channel` is the setting's value that sent it, such as `kitty` or `terminal_bell`. The `terminal_bell` channel rings the bell and shows neither your text nor the title.
213213
from line 224
224224
225225Your mod can't choose the channel, and Claude Code doesn't limit how often a mod notifies. The user's setting is the only switch.
226226
227When the call reaches a running session, the user's [`Notification` hooks](/docs/en/hooks#notification) run with the type `plugin_notification`. They run even when the answer is `disabled`, and in a session with no terminal, where the answer is `no-surface`. No hook runs for a call made before the session starts or while Claude Code is exiting. The hook your mod called `$.ui.notify` from is left out of that run, so a `classic.Notification` hook that notifies isn't called again for its own notification.
227When the call reaches a running session, the user's [`Notification` hooks](https://code.claude.com/docs/en/hooks#notification) run with the type `plugin_notification`. They run even when the answer is `disabled`, and in a session with no terminal, where the answer is `no-surface`. No hook runs for a call made before the session starts or while Claude Code is exiting. The hook your mod called `$.ui.notify` from is left out of that run, so a `classic.Notification` hook that notifies isn't called again for its own notification.
228228
229229A terminal doesn't report whether it showed the notification, so `isSent: true` means it was sent, not that the user saw it.
230230
from line 244
244244})
245245```
246246
247In the [VS Code extension](/docs/en/plugins/mods/interface.md#draw-in-the-vs-code-extension), status lines and toasts show above the prompt box instead, and log lines in the conversation.
247In the [VS Code extension](https://code.claude.com/docs/en/plugins/mods/interface.md#draw-in-the-vs-code-extension), status lines and toasts show above the prompt box instead, and log lines in the conversation.
248248
249249### Start a turn from a background job
250250
from line 252
252252
253253### Stop background work
254254
255Timers stop when the module reloads. For long-running work inside a hook, [`next.signal`](/docs/en/plugins/mods/reference#the-hook-function) is an `AbortSignal` that aborts when the event your hook is handling is abandoned, for example when the user interrupts, so pass it to anything long-running.
255Timers stop when the module reloads. For long-running work inside a hook, [`next.signal`](https://code.claude.com/docs/en/plugins/mods/reference#the-hook-function) is an `AbortSignal` that aborts when the event your hook is handling is abandoned, for example when the user interrupts, so pass it to anything long-running.
256256
257257### Calls and background work after the user interrupts a turn
258258
from line 262
262262
263263#### Retry a refused or dropped call
264264
265Make a refused or dropped call again from a [`turn.complete`](/docs/en/plugins/mods/events#follow-a-turn) hook. Calls stay refused until the user's next input, and nothing queues or repeats them for you.
265Make a refused or dropped call again from a [`turn.complete`](https://code.claude.com/docs/en/plugins/mods/events#follow-a-turn) hook. Calls stay refused until the user's next input, and nothing queues or repeats them for you.
266266
267267When a call's result or error contains one of these strings, keep its arguments for a later retry:
268268
from line 269
269269* **`the session was stopped and nobody has sent input since`**: the call was refused
270270* **`was removed from the queue before it ran`**: the prompt or command left the queue without running and was dropped, for example when the user stopped the turn
271271
272In this example, call `submitPrompt` from your background job in place of [`$.prompt.submit`](#start-a-turn-from-a-background-job). Put the `pendingText` variable and the `submitPrompt` function above [`register`](/docs/en/plugins/mods/reference#files), and the `turn.complete` hook inside it, because [validation](/docs/en/plugins/mods/create.md#check-what-claude-code-reads-from-your-mod) refuses `$` passed to a function declared inside `register`. When the result or the error carries one of those strings, `submitPrompt` keeps the text in `pendingText` for the `turn.complete` hook to submit again:
272In this example, call `submitPrompt` from your background job in place of [`$.prompt.submit`](#start-a-turn-from-a-background-job). Put the `pendingText` variable and the `submitPrompt` function above [`register`](https://code.claude.com/docs/en/plugins/mods/reference#files), and the `turn.complete` hook inside it, because [validation](https://code.claude.com/docs/en/plugins/mods/create.md#check-what-claude-code-reads-from-your-mod) refuses `$` passed to a function declared inside `register`. When the result or the error carries one of those strings, `submitPrompt` keeps the text in `pendingText` for the `turn.complete` hook to submit again:
273273
274274```javascript theme={null}
275275// The text of the last refused or dropped prompt, or null
from line 315
315315| :- | :- |
316316| `$.prompt.submit` | Resolves with `{ drop: '<reason>' }` and queues no prompt |
317317| `$.command.run` | Rejects with an error whose message ends with `<reason>` |
318| [`$.agent.spawn`](/docs/en/plugins/mods/reference#mods-api-methods) | Resolves with `{ deny: '<reason>' }` and starts no subagent |
318| [`$.agent.spawn`](https://code.claude.com/docs/en/plugins/mods/reference#mods-api-methods) | Resolves with `{ deny: '<reason>' }` and starts no subagent |
319319| `$.session.send` to a subagent that finished | Resolves with `{ isDelivered: false, reason: 'Agent "<id>" was not resumed: <reason>.' }`, where `<id>` is the subagent's id. A subagent that's still running receives the message. |
320| `$.session.send` to an [agent team](/docs/en/agent-teams.md) teammate | Resolves with `{ isDelivered: false, reason: 'Teammate "<name>" was not messaged: <reason>.' }`, where `<name>` is the teammate's name |
321| [`$.tool.call`](/docs/en/plugins/mods/reference#mods-api-methods) of Bash with `run_in_background: true`, Monitor, or CronCreate | Resolves with `{ deny: '<tool> was not run: it starts background work, a schedule or a goal, and <reason>' }`, where `<tool>` is the tool's name |
320| `$.session.send` to an [agent team](https://code.claude.com/docs/en/agent-teams.md) teammate | Resolves with `{ isDelivered: false, reason: 'Teammate "<name>" was not messaged: <reason>.' }`, where `<name>` is the teammate's name |
321| [`$.tool.call`](https://code.claude.com/docs/en/plugins/mods/reference#mods-api-methods) of Bash with `run_in_background: true`, Monitor, or CronCreate | Resolves with `{ deny: '<tool> was not run: it starts background work, a schedule or a goal, and <reason>' }`, where `<tool>` is the tool's name |
322322
323Along with these `$.session.send` and `$.tool.call` results, the user can see the [`Stopped:` toast](/docs/en/plugins/mods/troubleshoot#stopped-type-a-message-to-continue).
323Along with these `$.session.send` and `$.tool.call` results, the user can see the [`Stopped:` toast](https://code.claude.com/docs/en/plugins/mods/troubleshoot#stopped-type-a-message-to-continue).
324324
325325#### Work your mod started before the interruption
326326
from line 335
335335| An event from a monitor your mod started | The monitor keeps running. An event the monitor raises before the user's next input is dropped, except the notice that the monitor ended, which arrives after that input. | Nothing can be done |
336336| A subagent your mod spawned and later resumed with `$.session.send` that finishes before the user's next input | Its finish notice waits, and reaches the conversation after that input | Nothing needed |
337337
338When a scheduled job comes due or a monitor raises an event before the user's next input, the user can see the [`Stopped:` toast](/docs/en/plugins/mods/troubleshoot#stopped-type-a-message-to-continue).
338When a scheduled job comes due or a monitor raises an event before the user's next input, the user can see the [`Stopped:` toast](https://code.claude.com/docs/en/plugins/mods/troubleshoot#stopped-type-a-message-to-continue).
339339
340340## Send and receive messages between sessions
341341
342A mod can send a plain-text message to another of your sessions, to one of this session's subagents, or to a teammate in its [agent team](/docs/en/agent-teams.md). It can also observe the messages that arrive and leave.
342A mod can send a plain-text message to another of your sessions, to one of this session's subagents, or to a teammate in its [agent team](https://code.claude.com/docs/en/agent-teams.md). It can also observe the messages that arrive and leave.
343343
344344To send one, call `$.session.send({ to, text })`, which makes the same delivery the SendMessage tool makes. Set `to` by who receives the message:
345345
from line 371
371371| `session.receive` | A message arrives for this session, before Claude reads it | `e.text`, and `e.origin.kind`, such as `peer` or `peer-send-message` for another session or agent, `task-notification`, or `scheduled-trigger`. Return `{ consumed: reason }` to keep it from Claude. |
372372| `session.send` | A message is about to leave, from the SendMessage tool or a mod | `e.to`, `e.text`, and `e.origin.kind`, which is `model` or `plugin` |
373373
374A session set to [refuse inbound messages](/docs/en/cross-session-messaging.md#control-inbound-messages) refuses a message before `session.receive` fires, so a hook never sees it. A message that's held for your approval reaches the hook first, so a mod can read a message you haven't approved yet. The hook's `next(e)` rejects when the message isn't delivered.
374A session set to [refuse inbound messages](https://code.claude.com/docs/en/cross-session-messaging.md#control-inbound-messages) refuses a message before `session.receive` fires, so a hook never sees it. A message that's held for your approval reaches the hook first, so a mod can read a message you haven't approved yet. The hook's `next(e)` rejects when the message isn't delivered.
375375
376376The sender's name on a received message is whatever the sender wrote, so don't base a decision on it.
377377
from line 387
387387| `$.store` | A JSON key-value store of your plugin's own, kept between sessions |
388388| `$.env` | `get` and `set` environment variables. Write the name as a string literal. |
389389| `$.settings` | `read` what the settings files and managed policy hold |
390| `$.session` | `messages()` returns the transcript as a list of `{ role, text, toolUses }`. Also the working directory, model, and more. [`usage()`](/docs/en/plugins/mods/reference#mods-api-methods) returns context window use and plan limits. |
390| `$.session` | `messages()` returns the transcript as a list of `{ role, text, toolUses }`. Also the working directory, model, and more. [`usage()`](https://code.claude.com/docs/en/plugins/mods/reference#mods-api-methods) returns context window use and plan limits. |
391391| `$.mcp` | `call` a tool on a connected MCP server |
392392
393393Files, processes, and requests have a few rules of their own:
from line 395
395395* **Paths**: a relative path resolves against the working directory of the session, or of the subagent whose event the hook is handling
396396* **`$.fs.list`**: returns one directory's entries as `{ name, kind, size, isLink }` and isn't recursive
397397* **`$.process.run`**: takes an argument list and uses no shell. It resolves to `{ exitCode, stdout, stderr }` whatever the exit code. It rejects if the program can't start or is still running at the timeout, which is 30 seconds by default, so wrap it in `try` and `catch`.
398* **`$.http.fetch`**: follows up to five redirects. On a redirect to a different origin, it keeps only the `accept`, `accept-language`, `content-type`, and `user-agent` request headers you set and drops the rest, so a request that depends on another header, such as `Authorization`, can fail after that redirect. The [limits](/docs/en/plugins/mods/reference.md#limits) give its timeout and body sizes.
398* **`$.http.fetch`**: follows up to five redirects. On a redirect to a different origin, it keeps only the `accept`, `accept-language`, `content-type`, and `user-agent` request headers you set and drops the rest, so a request that depends on another header, such as `Authorization`, can fail after that redirect. The [limits](https://code.claude.com/docs/en/plugins/mods/reference.md#limits) give its timeout and body sizes.
399399
400Every one of these calls is itself an event, named for its namespace and method without the `$.`, such as `fs.read` for `$.fs.read`. A mod [earlier in the chain](/docs/en/plugins/mods/events.md#the-order-mods-run-in) can observe, rewrite, or refuse your call, which is how an organization restricts what mods reach.
400Every one of these calls is itself an event, named for its namespace and method without the `$.`, such as `fs.read` for `$.fs.read`. A mod [earlier in the chain](https://code.claude.com/docs/en/plugins/mods/events.md#the-order-mods-run-in) can observe, rewrite, or refuse your call, which is how an organization restricts what mods reach.
401401
402402A mod can refuse your `$.process.spawn` call after the command has produced output or exited, and nothing the command did is undone. The call then rejects with a message that ends with one of these strings and the refusing mod's reason:
403403
from line 406
406406
407407## Next steps
408408
409* [React to events](/docs/en/plugins/mods/events.md): hook tool calls, prompts, and turns
410* [Draw in the interface](/docs/en/plugins/mods/interface.md): show what your mod collects in a pane or above the prompt
411* [Test a mod](/docs/en/plugins/mods/test.md): stub any of these calls in a test
412* [Mods reference](/docs/en/plugins/mods/reference.md): events, mods API methods, and limits
409* [React to events](https://code.claude.com/docs/en/plugins/mods/events.md): hook tool calls, prompts, and turns
410* [Draw in the interface](https://code.claude.com/docs/en/plugins/mods/interface.md): show what your mod collects in a pane or above the prompt
411* [Test a mod](https://code.claude.com/docs/en/plugins/mods/test.md): stub any of these calls in a test
412* [Mods reference](https://code.claude.com/docs/en/plugins/mods/reference.md): events, mods API methods, and limits
413413
No line in this hunk matches that.