Hook `if` field is silently ignored for Edit/Write tools (works for Bash) — v2.1.100

Resolved 💬 2 comments Opened Apr 10, 2026 by vincent-snuailab Closed Apr 10, 2026

Version

Claude Code 2.1.100 (Windows 11, Node.js hook scripts).

Summary

The hook if field works for Bash tool patterns (e.g. Bash(git *)) but is silently ignored for Edit and Write tools. Any if value on a PostToolUse hook targeting Edit/Write causes the entire hook entry to be skipped — the hook never spawns, regardless of the value.

Related: #39612 (docs gap for if), #41376 (closed as dup of #36389), #36389, #36332.

Expected behavior

Per the undocumented if field (introduced v2.1.85 per #39612) using permission rule syntax, the following should filter a hook to only fire on matching file paths:

{
  "matcher": "Write|Edit",
  "hooks": [{
    "type": "command",
    "command": "node /path/to/script.js",
    "if": "Edit(*.resx)|Write(*.resx)"
  }]
}

The hook should fire on Edit/Write of .resx files and be skipped for other extensions.

Actual behavior

The hook never fires as long as the if field is present on an Edit/Write hook entry, regardless of the pattern value. Removing the if field makes the hook fire correctly on every Edit/Write.

Reproduction

.claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [{
          "type": "command",
          "if": "Bash(git *)",
          "command": "bash -c 'echo HOOK_FIRED_BASH >> /tmp/hook.log'"
        }]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [{
          "type": "command",
          "if": "Edit(**)|Write(**)",
          "command": "bash -c 'echo HOOK_FIRED_EDIT >> /tmp/hook.log'"
        }]
      }
    ]
  }
}

Steps:

  1. Ask Claude to run git status via Bash → HOOK_FIRED_BASH is appended ✅
  2. Ask Claude to run echo hello via Bash → log unchanged (correctly filtered out) ✅
  3. Ask Claude to Write any file → log unchanged ❌ (hook should have fired)
  4. Ask Claude to Edit any file → log unchanged ❌ (hook should have fired)
  5. Remove if from the PostToolUse entry → Write/Edit now fires the hook ✅

Patterns tested (all fail for Edit/Write)

| if value | Fires? |
|------------|--------|
| Edit|Write | ❌ |
| Edit(*)|Write(*) | ❌ |
| Edit(**)|Write(**) | ❌ |
| Edit(.*)|Write(.*) | ❌ |
| Edit(*.resx)|Write(*.resx) | ❌ |
| Edit(.*Resources\.resx)|Write(.*Resources\.resx) | ❌ |
| Edit(**/Resources.resx)|Write(**/Resources.resx) | ❌ |
| Edit(/.claude/hooks/test/Resources.resx) | ❌ |
| Edit(.claude/hooks/test/Resources.resx) | ❌ |
| Edit(/**/Resources.resx) | ❌ |
| Write(src/.*) (copying #41376 comment syntax) | ❌ |
| (removed entirely) | ✅ |

Equivalent Bash(git *) vs Bash(echo *) filtering in the same settings.json works correctly as expected — confirming the if field infrastructure is wired up but the Edit/Write code path is missing or broken.

Impact

The official workaround (filter inside the hook script by parsing tool_input.file_path from stdin) still works, but forces a Node process spawn on every Write/Edit even for unrelated files. For a codebase with frequent Edit/Write traffic this is measurable overhead, and defeats the purpose of having a declarative if filter at the configuration level.

Request

  1. Make Edit/Write if patterns consume tool_input.file_path consistently with how Bash consumes tool_input.command.
  2. Document the if field officially (currently absent from code.claude.com/docs/en/hooks; see #39612).
  3. Clarify the exact syntax — gitignore spec, permission rule syntax, or regex — since #39612 mentions permission rule syntax but existing permission rule docs describe gitignore patterns for Read/Edit.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗