[Feature Request] hooks[].if couples a tool name to the condition, so one condition across N tools costs N handlers

Status Open
Reported on v2.1.233
Maintainer reply None cached
Activity 0 comments · opened Aug 16, 2026

On v2.1.233, a hook's if condition has to name a tool, so a single condition that applies to several tools costs one handler per tool, each carrying a full copy of the hook's configuration.

The limitation

if uses permission rule syntax, which is Tool(specifier). The tool name is therefore part of the condition, and the hooks documentation spells out the consequence:

A single if rule matches only one tool's calls, so each tool gets its own handler.

matcher already selects which tools a handler applies to. When if names a tool as well, the two fields overlap, and the handler has to be cloned once per tool to restate a condition that never varies.

Concrete cost

A type: "prompt" hook that reviews every edit to a TypeScript source. The condition is identical for every tool; only the tool name changes:

{ "matcher": "Edit",
  "hooks": [{ "type": "prompt", "if": "Edit(**/*.ts)",      "prompt": "<policy>" }] },
{ "matcher": "Write",
  "hooks": [{ "type": "prompt", "if": "Write(**/*.ts)",     "prompt": "<the same policy, byte for byte>" }] },
{ "matcher": "MultiEdit",
  "hooks": [{ "type": "prompt", "if": "MultiEdit(**/*.ts)", "prompt": "<the same policy again>" }] }

Each handler names its tool twice, once in matcher and once again in if. The condition and the prompt are identical in all three; only the tool name differs, and any change has to be made in every copy and kept in sync. A fourth tool adds a fourth copy. A command hook pays this only once, because it can filter inside the script it already spawns. A prompt hook's entire configuration is the JSON entry, so the duplication is unavoidable.

Requested

Let if express a condition without naming a tool, so it filters the calls matcher has already selected:

{ "matcher": "Edit|Write|MultiEdit",
  "hooks": [{ "type": "prompt", "if": "**/*.ts", "prompt": "<policy, once>" }] }

matcher selects the tools and if filters the argument. One handler, one copy of the prompt, and it scales to any number of tools.

A bare pattern needs one decision: which argument it matches. The natural reading is the same argument that tool's permission-rule specifier already matches, so the file path for Edit, Write and MultiEdit, and the command for Bash.

If reusing the existing permission-rule grammar is preferred, "if": "*(**/*.ts)" would serve equally well, since * is already documented as a tool-name glob for deny and ask rules. That form currently never matches: on 2.1.233 a handler with "if": "*(**/*.ts)" did not fire for a Write to src/util.ts, while "if": "Write(**/*.ts)" fired for the same call. There is no warning, so a hook written that way is silently inert.

Related

#49629 asked for OR syntax in if, to combine several distinct rules in one handler. It was closed as not planned and is now locked. That is an adjacent but different gap: this request is about a single condition that spans several tools, which OR syntax would express only by repeating the same pattern once per tool.

Environment: Claude Code 2.1.233, Windows 11 (win32).

View original on GitHub ↗