Follow Discord
Sweep 22 Oct 2026 · 04:32Z Build v2.1.308 527 read Stable v2.1.308 Latest v2.1.308 Next v2.1.308 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 20 Oct 2026 19:07 UTC.

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

#### Check whether a tool call is read-only #### Filter a `tool.check` hook by whether the call is read-only

The whole hunk

from line 177, old and new numbered
/
lines
from line 177
177177 
178178#### Approve or refuse a tool call before the user is asked
179179 
180To decide whether a tool call may run, handle [`tool.check`](/docs/en/plugins/mods/reference#tools), the event where Claude Code makes that decision. It fires after the permission rules and the settings hooks have decided, and `next(e)` resolves to their decision: `allow`, `ask`, or `deny`. Your hook returns that decision or a different one. `e.input` holds the tool's arguments, such as `command` for Bash.
180To 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 
182182For 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.
183183 
184* **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.
185* **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.
186* **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
188 
184189This hook refuses `git push` while the current branch is `main`:
185190 
186191```javascript theme={null}
187192on('tool.check', { tool: 'Bash' }, async ($, e, next) => {
188 // What the permission rules and settings hooks decided: 'allow', 'ask', or 'deny'
193 // Claude Code's own decision
189194 const decided = await next(e)
190195 if (!e.input.command.includes('git push')) return decided
191196 const branch = await $.process.run(['git', 'branch', '--show-current'])
from line 210
205210>
206211> 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).
207212 
213#### Check whether a tool call is read-only
214 
215In a [`tool.check`](#approve-or-refuse-a-tool-call-before-the-user-is-asked) hook, `e.isReadOnly` is `true` when the tool reports the call as read-only. Otherwise the field is absent, not `false`. `e.isReadOnly` requires Claude Code v2.1.304 or later.
216 
217Some 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:
218 
219* **Bash**: the command passes Claude Code's built-in [read-only command check](/docs/en/permissions#read-only-commands)
220* **An MCP tool**: the server sets the tool's `readOnlyHint` annotation to `true`
221 
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.
223 
224This hook returns an `ask` for every `mcp__github__` call that isn't read-only and wasn't already denied:
225 
226```javascript theme={null}
227on('tool.check', { tool: /^mcp__github__/ }, async ($, e, next) => {
228 const decided = await next(e)
229 // Read-only calls and denied calls pass through unchanged
230 if (e.isReadOnly || decided.decision === 'deny') return decided
231 return { decision: 'ask', reason: 'This tool can change the GitHub repository' }
232})
233```
234 
235#### Filter a `tool.check` hook by whether the call is read-only
236 
237To limit which calls your hook handles by [`e.isReadOnly`](#check-whether-a-tool-call-is-read-only), use the [matcher](#filter-which-events-a-hook-handles) for read-only calls and the hook body for the rest:
238 
239* **Read-only calls only**: add `isReadOnly: true` to the matcher, as in `on('tool.check', { tool: 'Bash', isReadOnly: true }, hook)`
240* **Calls that aren't read-only**: test `e.isReadOnly` in the hook body, as the [`mcp__github__` example](#check-whether-a-tool-call-is-read-only) does. If you put `isReadOnly: false` in a matcher, your hook never runs.
241 
208242### Rewrite or add to a prompt
209243 
210244A `prompt.submit` hook sees each prompt before the turn starts, so it can rewrite the text or add to it. `e.text` is what was typed.
from line 342
308342* **`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.
309343* **`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.
310344 
311[`tool.check`](#approve-or-refuse-a-tool-call-before-the-user-is-asked) fires after the permission rules and both groups of `PreToolUse` hooks have decided. To learn which of those 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#precedence-among-rules-hooks-and-mods).
312346 
313347### Handle a hook that fails
314348 
Feedback