Hooks silently dropped when allowManagedHooksOnly is set, and the PreToolUse counter still increments
Summary
When a managed policy sets allowManagedHooksOnly, hooks configured in.claude/settings.json are silently dropped. Nothing at any surface a user
looks at says so — and the PreToolUse telemetry counter keeps incrementing,
which actively points away from the cause.
This cost me two weeks and a wrong bug report (#88896, now closed by me with a
correction). Filing the observability gap separately because it is the part that
is actionable on your side; the suppression itself is working as designed.
What the user sees
.claude/settings.jsonwith aPreToolUsehook is accepted. No warning, no
error, no startup notice.
- Every guarded tool call runs unimpeded.
~/.claude.jsonshows the event being counted:
````
pre_tool_hook_duration_ms_count : 108
pre_tool_hook_duration_ms_avg : 0.074 ms
pre_tool_hook_duration_ms_min/max : 0 / 1
A counter at 108 reads as "dispatched 108 times". Combined with ~0 ms it
reads as "dispatched but the process never spawned" — which is precisely the
wrong conclusion, and it is the one I reported. The honest reading is "the
event fired, found no permitted hook, and returned", but nothing
distinguishes those two from outside.
Where the truth is, and how hard it is to reach
Only in --debug output, and only if you already suspect it:
[DEBUG] Registered 0 hooks from 0 plugins
[DEBUG] Skipping plugin hooks - allowManagedHooksOnly is enabled and no managed plugins
[DEBUG] Hooks: Found 0 total hooks in registry
Two problems with that as the only signal:
- You have to know to run
--debugand know what to grep for. Someone
suggested exactly that in the first reply to #88896 and I still did not
connect it, because I had no reason to suspect policy.
- The message names plugin hooks. My
.claude/settings.jsonhooks are not
plugin hooks, so even having read that line, it does not obviously explain
them. Found 0 total hooks in registry is the line that does, and it does not
mention a cause.
Suggested fix
Any one of these would have ended it in seconds:
- A session-start notice when configured hooks were dropped:
2 hooks from .claude/settings.json were not registered: allowManagedHooksOnly is set by managed settings
/hooksshows suppressed hooks as suppressed, with the reason, rather
than listing them as if they were active or omitting them silently.
claude doctorreports it.- Do not increment
pre_tool_hook_duration_ms_countwhen no permitted hook
exists, or split the metric. A counter that rises for an event with nothing
to run is a false positive that survives every other check.
(1) is the smallest and covers the case.
Why this matters more than a usual DX gap
Hooks are the documented mechanism for compliance and safety gates. Ours refuse
writes to a production studio share, refuse a deploy that holds no resource
lock, and refuse an agent editing its own guard. For two weeks our
documentation described enforcement that was not running.
A silently disabled guard is worse than an absent one, because the absent
one is noticed. This is the same class another commenter raised on #88896 —
they now run a canary that feeds a hook a synthetic payload and fails loudly if
the documented verdict does not come. That mitigation exists because the product
gives no signal, and it should not have to.
Note the trap this creates for the obvious in-repo test: guard self-tests that
pipe a payload directly to the hook script stay green forever. They prove the
script's logic and never its invocation. Ours did exactly that.
Environment
claude --version 2.1.263 (also seen on 2.1.240, 2.1.241)
OS Windows 11 Enterprise 10.0.26100
policy ~/.claude/remote-settings.json, forceRemoteSettingsRefresh: true
allowManagedHooksOnly: true
managed hooks defined: PostToolUse only
Related
- #88896 — my own report of this as a
PreToolUsedispatch bug. **Closed as not
planned**, with a correction, because the premise was wrong for my install.
Please do not treat this issue as a reopening of that one.
- #79480 — "PreToolUse hooks in project
.claude/settings.jsonare silently not
registered." May be a genuinely separate defect; on my machine the policy is a
sufficient explanation, so my evidence should not be used to support or
dismiss it.
What I have not verified
The debug line names plugin hooks specifically. That allowManagedHooksOnly
also suppresses .claude/settings.json hooks is inferred from Found 0 total plus the timings above, not read directly from a message that
hooks in registry
says so. There is only one --debug log on this machine, from before the flag
was noticed, so I have not run the decisive control (a fresh claude --debug
grepped for my hook script's name).
If that control shows my hooks are registered, this issue is still valid as
written — the missing signal is the subject either way — but the specific cause
I attribute it to would be wrong, and I will say so here.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗