Add permission_decision field to hook input and a ToolUseDenied hook event
Context
When building automation on top of Claude Code hooks (e.g., logging tool use patterns to optimize permission rules), the hook input lacks key fields needed to understand the permission lifecycle.
Problem / Motivation
Two gaps in the current hook data model make permission-aware automation harder than it needs to be:
1. permission_mode doesn't reflect per-tool-use permission state
The permission_mode field in hook input is always "default" — it reflects the session's permission mode, not whether the specific tool use was auto-allowed or prompted. There's no way for a hook to know whether a given tool invocation required user approval.
Observed behavior (tested 2026-03-21 on Linux):
// Auto-allowed command (echo is in allow list):
{"permission_mode": "default", "tool_name": "Bash", "tool_input": {"command": "echo test"}}
// Prompted command (not in allow list):
{"permission_mode": "default", "tool_name": "Write", "tool_input": {"file_path": "/tmp/test.txt"}}
Both show "default" — there's no distinction.
2. No hook event fires when the user denies a tool use
PreToolUsefires before the permission promptPostToolUsefires only after successful execution- If the user denies, nothing fires
Denials are a strong signal — they indicate commands the user actively doesn't want. This data is valuable for permission auditing, safety logging, and workflow optimization.
Current workaround: Log all PreToolUse events, log all PostToolUse events, correlate by tool_use_id — a Pre without a matching Post is inferred as denied. This works but is indirect and can't distinguish denials from session-end interruptions.
Proposed Solution
Option A: Enrich existing hook input (minimal change)
Add a permission_decision field to PreToolUse and/or PostToolUse input:
{
"permission_decision": "allowed" | "prompted" | "denied",
"tool_name": "Bash",
"tool_input": {...}
}
"allowed"— command matched an allow rule, no prompt shown"prompted"— user was shown a permission prompt (and approved, if this is PostToolUse)"denied"— user rejected the prompt (would require PostToolUse to fire on denial too)
Option B: Add a ToolUseDenied hook event (more expressive)
Fire a new hook event when the user denies a tool use:
{
"hook_event_name": "ToolUseDenied",
"tool_name": "Write",
"tool_input": {"file_path": "/tmp/test.txt"},
"tool_use_id": "toolu_xyz",
"user_comment": "this is readonly, don't write files"
}
Including user_comment (if the user typed a reason when denying) would be especially valuable — it captures intent that's otherwise lost.
Ideal: Both
Option A (enrich existing fields) + Option B (ToolUseDenied event) together would give hooks complete visibility into the permission lifecycle with minimal API surface.
Acceptance Criteria
- [ ] Hook consumers can distinguish "auto-allowed" from "user was prompted" tool invocations
- [ ] Hook consumers are notified when a user denies a tool use
- [ ] Denial notifications include the user's comment/reason if one was provided
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗