[BUG] PreToolUse hook "if" field (permission-rule syntax) never matches Skill tool invocations (v2.1.278)

Status Open
Reported on v2.1.278
Maintainer reply None cached
Activity 0 comments · opened Sep 21, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Expected: A PreToolUse hook with an "if" field using permission-rule syntax, e.g. "if": "Skill(subagent-driven-development)", should only fire the hook command when the Skill tool is invoked with a matching skill name — mirroring how the identical rule string works as a top-level permissions.deny/permissions.allow rule, and how "if": "Bash(git *)" correctly gates Bash invocations.

Actual: The if condition has no gating effect at all. With "matcher": "Skill" and no if, the hook fires on every Skill invocation, as expected. Adding "if": "Skill(subagent-driven-development)" (or the Skill(name ) / Skill(namespaced:name ) variants) changes nothing observed: the hook still fires on every Skill invocation, matching or not, exactly as if the if field were absent. The same rule string used as a top-level permissions.deny entry correctly matches/blocks the named skill, confirming the rule syntax itself is valid and accepted — the gap is specifically in PreToolUse's if evaluation against Skill tool calls. The equivalent pattern for Bash ("if": "Bash(<pattern>)") works correctly, so this appears isolated to the Skill tool's if evaluation path.

What Should Happen?

The if condition should evaluate against the invoked skill and only fire the hook when the skill name matches — the same way top-level permissions.deny/permissions.allow rules and Bash's "if": "Bash(<pattern>)" already do.

Current workaround: drop the if field entirely, keep "matcher": "Skill" (fires unconditionally on every Skill invocation), and do the filtering manually inside the hook script by inspecting tool_input.skill from the hook's JSON stdin payload:

const input = JSON.parse(await readStdin());
if (input.tool_input?.skill !== "subagent-driven-development") process.exit(0);
// ... proceed with hook logic

This works reliably but defeats the purpose of declarative if filtering and pays the cost of spawning the hook process on every Skill call.

See also #62108 — a related feature request for a dedicated active_skill field in PreToolUse, which would also resolve this gap.

Error Messages/Logs

No error is thrown — this is a silent behavior gap, not a crash. The hook process runs successfully but the if condition has no observable effect on whether it fires.

Steps to Reproduce

  1. Create a project with a skill named subagent-driven-development (or any custom skill).
  2. In settings.local.json, register a PreToolUse hook with "matcher": "Skill" and a command that writes a marker (e.g. appends a line to a log file) when it runs. Confirm baseline: invoke any skill via the Skill tool — the marker is written every time, the hook fires unconditionally, as expected with no if.
  3. Add "if": "Skill(subagent-driven-development)" to the same hook entry, without changing anything else.
  4. Invoke a skill that is not subagent-driven-development via the Skill tool. Observe: the marker is still written — the hook fires despite the if condition not matching.
  5. Invoke subagent-driven-development itself via the Skill tool. Observe: the marker is written too — no difference in behavior from step 4, confirming the if clause has zero observable effect in either direction.
  6. Compare: replace the hook's if with the same string under top-level permissions.deny — it correctly blocks/matches. Replace the tool in the if string with Bash(<pattern>) on a Bash-triggering hook — it correctly gates.

Claude Model

None

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.278

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

Windows Terminal

Additional Information

Confirmed via two independent tests in a fresh subprocess (no session state):
invoking Skill(subagent-driven-development) with the if condition active still
fired the hook unconditionally, identical to invoking an unrelated skill
(Skill(dispatching-parallel-agents)) — both produced the same hook behavior,
confirming if had zero discriminating effect between matching and non-matching
skill invocations.

View original on GitHub ↗