PreToolUse hook permissionDecision: allow no longer overrides a permissions.ask glob (2.1.220; regression vs. documented order)
What happened
A PreToolUse hook returning permissionDecision: "allow" no longer takes precedence over a matching permissions.ask glob. The command is denied (or prompts) despite the hook's explicit allow.
This appears to be a regression: the same setup honored the hook allow when we last measured it in March 2026. Re-measuring on 2.1.220 shows the ask glob winning.
Impact is concentrated in unattended/automated use: a hook that exists precisely to auto-approve a narrow, verified-safe command shape can no longer do so. In headless -p runs the tool call lands in permission_denials[]; in interactive sessions with --permission-mode auto the session stops at a permission menu and waits indefinitely for a human. Curating the ask-glob set is now the only lever for that friction, which means removing a guard entirely where a hook previously provided a targeted, conditional allow.
Expected behavior
Per the documented resolution order, a hook allow decision should outrank an ask rule (an ask should only apply when no hook has already allowed the call). A deny glob outranking everything is expected and unchanged.
Minimal reproduction
- In a project, create
.claude/settings.json:
{
"permissions": { "ask": ["Bash(mkfifo *)"] },
"hooks": {
"PreToolUse": [
{ "matcher": "Bash", "hooks": [
{ "type": "command", "command": "python3 .claude/hooks/allow.py" } ] }
]
}
}
.claude/hooks/allow.py:
import json, sys
json.load(sys.stdin)
print(json.dumps({"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "probe"}}))
- Run:
claude -p --output-format json "Run exactly this bash command and nothing else: mkfifo probe.fifo"
Observed: no probe.fifo is created, and the Bash call appears in permission_denials[].
Control: remove only the permissions.ask block and re-run — the FIFO is created (the hook allow is honored), which isolates the ask glob as the sole difference.
Measurements
12 arms, all valid, on 2.1.220 (2026-07-29), each arm verifying the command text matched a sentinel and using artifact existence as execution ground truth (with permission_denials[] as a secondary channel; disagreement was treated as invalid rather than a result):
| Arm | Ask glob present? | Hook decision | Result |
|---|---|---|---|
| baseline (--permission-mode auto) | no | allow | executed — allow honored |
| baseline (default mode) | no | allow | executed — allow honored |
| measure (auto) | yes | allow | denied |
| measure (default) | yes | allow | denied |
| second allow hook added | yes | allow ×2 | denied |
| twin control | deny glob instead | allow | denied (expected) |
Also observed with the ask rule defined at user level rather than project level, so this is not project-over-user settings precedence. Replaying the identical payloads directly against the hook returns permissionDecision: allow with exit 0 in every case, so the hook's output is not in question — only how the harness combines it with the ask rule.
Environment
- Claude Code 2.1.220
- macOS (darwin 25.6.0)
- Reproduced in both
--permission-mode autoanddefault, headless-p - Interactive sessions show the same effect as a permission menu that a hook allow should have prevented
Note
Happy to provide the probe script or additional arms if useful. If the new ordering is intentional, updating the permission-precedence documentation would help — the current docs read as hook allow outranking ask, which is what automated setups have been built against.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗