PreToolUse hook permissionDecision: allow no longer overrides a permissions.ask glob (2.1.220; regression vs. documented order)

Status Fixed / completed
Reported on v2.1.220
Maintainer reply None cached
Activity 2 comments · opened Jul 30, 2026 · closed Aug 25, 2026

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

  1. In a project, create .claude/settings.json:
{
  "permissions": { "ask": ["Bash(mkfifo *)"] },
  "hooks": {
    "PreToolUse": [
      { "matcher": "Bash", "hooks": [
        { "type": "command", "command": "python3 .claude/hooks/allow.py" } ] }
    ]
  }
}
  1. .claude/hooks/allow.py:
import json, sys
json.load(sys.stdin)
print(json.dumps({"hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "permissionDecisionReason": "probe"}}))
  1. 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 auto and default, 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.

View original on GitHub ↗

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