{"page":"events","title":"The event catalogue","blurb":"Every event you can hook, what each one carries, and how a matcher narrows it.","build":"2.1.286","sections":["Engine events","Operation events","classic.\\*","Globs","Events you can ask to rerun","What still has no event","Why use hooks at all"],"limit":8000,"section":null,"offset":0,"count":7517,"total":21129,"next_offset":7517,"next_url":"https:\/\/changelogs.core-directive.com\/reference\/mods\/events.json?offset=7517","markdown":"This page is the map of every event at 2.1.286. The exact shape of each one is in the declarations, which Claude Code writes into `.claude-plugin\/types\/` beside your mod each time it loads it ([Anthropic's create page](https:\/\/code.claude.com\/docs\/en\/plugins\/mods\/create#get-the-types-for-your-build) lists the files). The `\/plugin-types` command that used to write them on request is gone at 2.1.287. At 2.1.286 they're 14,711 lines, which is too much for a web page, and a lot of that is tool input shapes you already know from using Claude Code.\n\nThere are 43 engine events in the 2.1.286 declarations, which the engine raises itself, and every verb on `$` is an event too. Four are new since 2.1.283: `telemetry.log` and `telemetry.mark` arrived in 2.1.284, `session.append` in 2.1.285 and `prompt.compose` in 2.1.286. They're all in the tables below.\n\n## Engine events\n\n### Tools\n\n| Event | When | Core's side effect |\n|---|---|---|\n| `tool.call` | A tool is about to run | **Yes**, running it |\n| `tool.check` | Permission is being decided | No |\n| `tool.describe` | The tool's description is being put together for the model | No |\n\n`tool.call` is the one with a side effect, so it's the one where not calling `next` is how you stop something. `tool.check` lets you have a say in the permission decision without owning the call, and it's the safer place for a policy that only wants a say. Its arguments are `{ tool, input, tool_use_id }` and it answers `{ decision: 'allow' | 'ask' | 'deny', reason?, rule? }`.\n\n`tool.call` hands you the tool's input flat, where `tool.check` nests it under `input`. A Bash call's command is `e.command`, and at 2.1.286 a hook that read `e.input.command` threw `TypeError: undefined is not an object (evaluating 'e.input.command')`.\n\nTo stop a call, return `{ deny: reason }` from `tool.call` without calling `next`. At 2.1.286 the model got the reason as an error result, `<tool_use_error>modsA tool.call says no<\/tool_use_error>` with `is_error: true`, and an empty reason gave it `<tool_use_error><\/tool_use_error>`. A deny from a `classic.PreToolUse` hook lands differently. The call resolves as an error whose text is `Error: ` and the reason, and a deny with no reason reads `Error: Hook PreToolUse:<tool> denied this tool`, a wording the 2.1.285 bundle added and 2.1.286 still has. The test kit on the [testing page](\/reference\/mods\/testing) produces that at 2.1.286. A `tool.call` deny never did. I couldn't see a classic deny in a live session either, because on this managed box `cc-plugin-sec-default` bypasses an installed plugin's classic hooks (more under classic.\\* below), and still did at 2.1.287, so the live wording is unverified.\n\nA `tool.call` raised inside a subagent carries `agentId`, and `$.agent.list()` turns that into a name, a parent and a type. Which plugin raised something and which agent loop it's in are two separate questions.\n\n### Prompts and turns\n\n| Event | When |\n|---|---|\n| `prompt.edit` | The person edits the prompt box, a key or a paste. New in 2.1.283. |\n| `prompt.submit` | A prompt is being sent |\n| `prompt.fill` | Something is writing the prompt box |\n| `prompt.suggest` | Dim suggestion text is being proposed into it |\n| `prompt.section` | A section of the system prompt is being built |\n| `prompt.compose` | The whole system prompt is being put together from its sections. New in 2.1.286. |\n| `prompt.context` | Context is being attached to a prompt |\n| `prompt.attachment` | The engine is injecting a message of its own for the model, a reminder or a mentioned file. New in 2.1.283. |\n| `turn.start` | A turn begins |\n| `turn.step` | The model is producing a step, streamed |\n| `turn.complete` | A turn settles, and usage numbers become real |\n\nOn `prompt.submit`, `e.context` is `string[] | undefined` and it only adds. What you add gets appended and doesn't replace anything. The result can also be `{ drop: reason }`. The `diff` mod budgets what it adds against a character limit, minus whatever other plugins already added, and when its part won't fit it **drops its own part and keeps the prompt**, then tells the person through `$.ui.status`. Any mod that attaches context should work the same way.\n\n`prompt.edit` fires on every edit, with the draft before it and the splice (`start`, `end`, `inputText`). A burst of keys counts as one edit. You can rewrite `inputText` on the way down or the box on the way up, and returning `{ text: e.text, cursor: e.cursor }` without calling `next` swallows the edit.\n\n`prompt.attachment` fires once for each message the engine injects by itself. Return `{ text: null }` to leave one out. The answer is remembered per attachment for the whole process, so it's asked again only on resume or after `$.ui.invalidate('prompt.attachment')`, and the transcript still keeps the engine's own copy.\n\n`prompt.section`'s answer is cached too. At 2.1.286 my hook on `env_info_simple` ran once across a three-turn session, and every request carried its first answer.\n\n`prompt.compose` gives you the whole system prompt from `next(e)` as a list of sections, each `{ id, text, scope }`, in the order they're sent. The declarations say you can add, replace, reorder or drop them, or skip `next` and write the list yourself. I measured adding a section, and a list in the wrong order being thrown out. It runs every turn. At 2.1.286, though, only the first answer of a session reached the API in my runs. Anthropic's events page says changed section text invalidates the prompt cache ([rewrite or add to a prompt](https:\/\/code.claude.com\/docs\/en\/plugins\/mods\/events#rewrite-or-add-to-a-prompt)), which suggests a changed answer gets sent, and I haven't re-run mine at 2.1.287 (more under \"Events you can ask to rerun\" below). [Gotcha 10](\/reference\/mods\/gotchas) has that and the cache rules, so read it before you ship one.\n\n### Sessions and agents\n\n| Event | When |\n|---|---|\n| `session.start` | A plugin loads into a session. Where most mods set themselves up. |\n| `session.receive` | Something arrives in the session |\n| `session.send` | A plain-text message is leaving for another agent or session. New in 2.1.283. |\n| `session.measure` | Usage moved, after a turn or when a rate limit moves a whole point. New in 2.1.283. |\n| `session.compact` | The transcript is being compacted |\n| `session.attach` | A surface attaches |\n| `session.detach` | A surface detaches |\n| `session.end` | The session ends. New in 2.1.283. |\n| `session.append` | A row is about to be stored in a conversation: a prompt, a response block, a tool result, a notice. New in 2.1.285. |\n| `agent.offer` | A subagent is being offered to the model |\n| `agent.spawn` | A subagent is being started |\n\n`session.start` fires once per process for each plugin, before the first prompt. After that it fires again only when that plugin loads fresh: it's enabled, the worker restarts, or a hot reload touches its module. It never fires for `\/clear`. The first one is awaited, so a tool you register there is listed by turn one.\n\n`session.end` fires once on exit, `\/clear`, resume, logout, a signal or the end of a `-p` run, after the settings-file `SessionEnd` hooks, and `e.reason` says which. The whole chain shares one short time limit so quitting stays fast, and the [lifecycle page](\/reference\/mods\/lifecycle) explains how that works.\n\n`session.measure` is how you watch usage without polling. It hands you the same figures `$.session.usage()` would, pushed as they change, so you compare them with your own threshold there.\n","previous_page":"engine","next_page":"ui","url":"https:\/\/changelogs.core-directive.com\/reference\/mods\/events","index_url":"https:\/\/changelogs.core-directive.com\/reference\/mods.json"}