No way to see which permission rule, hook or classifier decided a tool call

Status Open
Reported on v2.1.274
Maintainer reply None cached
Activity 0 comments · opened Sep 22, 2026

Summary

There is no way to see, after the fact, which permission rule or hook decision resolved a tool call. Nothing in --debug, the transcript JSONL, or any hook event records "this call was allowed by rule X" / "asked because of rule Y" / "hook Z returned ask". Maintaining a non-trivial permission configuration (we have ~250 Bash rules across three settings files plus a PreToolUse hook) is guesswork without it.

What is missing

For each tool call, a record of:

  • the final outcome (allow / ask-then-approved / ask-then-denied / deny)
  • what produced it: the matching rule's text and which settings file it came from, the hook and its permissionDecision, or the auto-mode classifier
  • for ask, whether the prompt was shown or resolved automatically

Either an opt-in log file (a permissions.decisionLog path, or lines in the existing debug log) or a hook event fired after the permission outcome is decided (PostPermission or similar, with the same payload) would do. A hook event is more general, since it can feed any sink.

Why it matters

Three things we have found by accident that a decision log would have shown on day one:

  1. A broad ask rule in ~/.claude/settings.json silently voided a narrower allow in the project file for weeks. The docs are clear that lists merge and ask beats allow, but nothing at runtime says "this allow never matches because that ask does".
  2. Seven mcp__render__* ask rules matched nothing after the server became a plugin (tools were renamespaced to mcp__plugin_render_render__*). A rule that matches nothing looks identical to one that never had to fire.
  3. A PreToolUse hook allow for trash inside a worktree was overruled on every call by a Bash(trash *) ask rule for months. The hook's reason was never displayed, so the dialog looked like a rule prompt with no way to tell which layer raised it.

In all three, the only way to distinguish "the rule fired" from "the rule is dead" was to reason about pattern text, because approvals are not observable and the model cannot see whether a call was gated.

We now log our own hook's decisions to a file from inside the hook, but that covers one layer of three and cannot see rules or the classifier.

Environment

Claude Code 2.1.274, macOS, default permission mode auto, seatbelt sandbox enabled.

Related

  • #86754 and #89561 are both about ask decisions being resolved without a visible prompt; a decision log would make that class of bug diagnosable from the user's side.

View original on GitHub ↗