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

Resolved 💬 4 comments Opened Apr 10, 2026 by christian289 Closed Apr 13, 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 4 comments on GitHub. Read the full discussion on GitHub ↗