[BUG] PreToolUse hooks are registered and listed by /hooks, but never execute (v2.1.219, Windows)

Status Open
Reported on v2.1.219
Maintainer reply None cached
Activity 1 comment · opened Aug 11, 2026

Summary

Two PreToolUse hooks configured in ~/.claude/settings.json are correctly recognized by the CLI (/hooks lists them with the right matchers) but are never invoked for any tool call. SessionStart hooks from the same settings file run normally, so the file is read and parsed. The failure is silent: no error, no warning, and the tool call proceeds as if no hook existed.

This is a safety-relevant failure mode: a hook that never fires is indistinguishable from a hook that fires and approves.

Environment

  • Claude Code 2.1.219
  • Windows 11 (26200), Git Bash as the Bash tool shell
  • Node v22.18.0 (node on PATH)
  • Permission mode: reproduced in both auto and default
  • No managed policy (C:/ProgramData/ClaudeCode/managed-settings.json does not exist), no hook-disabling env vars

Configuration (~/.claude/settings.json)

{
  "hooks": {
    "PreToolUse": [
      { "matcher": "Write", "hooks": [{ "type": "command", "command": "node \"C:/Users/<me>/.claude/hooks/protect-large-files.js\"" }] },
      { "matcher": "Bash",  "hooks": [{ "type": "command", "command": "node \"C:/Users/<me>/.claude/hooks/guard-destructive-bash.js\"" }] }
    ],
    "SessionStart": [ /* two bash scripts — these DO run */ ]
  }
}

/hooks shows:

Hooks — 4 hooks configured
1. PreToolUse (2)
   1. [User] Bash    1 hook
   2. [User] Write   1 hook

Steps to reproduce

  1. Register a PreToolUse hook with matcher Bash that (a) writes a line to a log file and (b) returns

{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"..."}}.

  1. Start a new session (started after the settings change — verified via the session transcript's first timestamp).
  2. Have Claude run a Bash command the hook matches, e.g.

T=$(mktemp -d); git -C "$T" init -q; echo x > "$T/untracked.txt"; git -C "$T" clean -fdx

Expected: permission prompt showing permissionDecisionReason.
Actual: command executes immediately. No prompt. The hook's log file is never created — i.e. the hook process is not started at all (this is not a case of the decision being ignored).

Evidence / what was ruled out

| Check | Result |
|---|---|
| Hook script run manually with the same JSON on stdin | Returns the correct ask JSON, exit 0 |
| Hook writes a log line whenever it matches | Log file never created → process never spawned |
| Session started after the settings change | Yes (transcript first timestamp 09:19Z vs settings mtime 05:08 local) |
| SessionStart hooks in the same file | Run fine (produced their output at session start) |
| Permission mode auto vs default | No difference |
| Sandboxed vs non-sandboxed Bash | No difference |
| permissions.allow pre-approving the command | No matching rule |
| Second PreToolUse hook (matcher Write) | Also never fires: overwriting a 100-line .js file with 1 line via Write produced no prompt |
| managed-settings.json / hook-disabling flags | None present |

So: both matchers, both permission modes, both sandbox states → PreToolUse never executes, while SessionStart from the same file does.

Impact

PreToolUse is the only mechanism that can inspect the content of a command (permissions.deny only prefix-matches the literal string, so rm -fr, git push -f, and git -C <path> clean bypass it). With PreToolUse silently dead, users who migrated their guardrails from deny to hooks are unprotected and have no signal that anything is wrong.

Possibly related

  • #85613 — settings.json in an ancestor directory silently ignored (different root cause: here the file is read, since SessionStart works and /hooks lists the hooks)
  • The /hooks menu lists PermissionDenied — After auto mode classifier denies a tool call, suggesting the permission pipeline was recently reworked; the PreToolUse call site may have been dropped on the new path.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗