[FEATURE] PermissionRequest and PermissionResponse hook events for metrics
Problem
We want to measure how much time Claude Code spends blocked waiting for user permission responses. This is important for understanding developer productivity impact and whether permission prompts cause significant workflow interruptions.
Currently, there's no way to capture:
- When a permission prompt is displayed to the user
- How long the user takes to respond
- What the user's response was (allow, deny, allow_always, deny_always)
- Metrics for denied permissions (since the tool never executes)
Related: #20213 requests StartedToolUse which would help, but doesn't capture the response type or denied permissions.
Proposed Solution
Add two new hook events:
PermissionRequest
Fired when Claude Code displays a permission prompt to the user.
{
"hook_event_name": "PermissionRequest",
"session_id": "abc123",
"tool_use_id": "toolu_01ABC...",
"request_id": "perm_req_12345",
"timestamp_ns": 1706547200000000000,
"tool_name": "Bash",
"tool_input": { "command": "npm install lodash" },
"permission_type": "tool_execution"
}
PermissionResponse
Fired when the user responds to a permission prompt.
{
"hook_event_name": "PermissionResponse",
"request_id": "perm_req_12345",
"timestamp_ns": 1706547205000000000,
"response": "allow",
"tool_name": "Bash",
"tool_input": { "command": "npm install lodash" }
}
Where response is one of: allow, deny, allow_always, deny_always
Use Cases
- Productivity metrics: Track total time per session blocked on permission prompts
- UX optimization: Identify which tool types cause the most permission friction
- Permission tuning: Understand grant/deny patterns to improve default allow rules
Open Questions
- How should timeout/cancellation be represented (user closes terminal without responding)?
- Will MCP tool permissions use the same events?
- How does batched permission approval work with these events?
Implementation Ready
We've already built plugin hooks ready to consume these events: https://github.com/intercom/claude-plugins/pull/181
The hooks sanitize sensitive data (env vars, tokens) before sending metrics to our observability platform.
Showing cached comments. Read the full discussion on GitHub ↗
8 Comments
+1 for this feature! I have a related use case:
Use Case: External status indicators (tmux, polybar, waybar, etc.)
I'm using hooks to update my tmux status bar to show Claude's state:
PermissionRequest→ Yellow status (Claude needs input)Stop→ Green status (Claude idle)The problem: there's no hook to clear the yellow when I approve a permission. Currently:
PreToolUsefires before the permission checkPostToolUsefires after the tool completesSo for long-running commands, the status stays yellow during the entire execution even though I've already approved. The
PermissionResponsehook would let me clear the status immediately on approval.Example config:
This would make external status indicators accurately reflect when Claude actually needs attention vs when a tool is just running.
+1 to this
I too would like this. My use case is also updating External status indicators and having the "permissions accepted but tool not finished" for things like
npm run buildor linters that take several minutes leaves quite a gap where we cannot know if the claude session is "waiting for input" or not.The case get's quite a lot more complicated when taking into account Agent Teams which can also fire off permissions requests.
+1 for this. I'd also like to use this to know which worktree to switch to (and I also want to ensure this issue doesn't get marked as stale)
+1; i have the same use case as @chrisae9
My use case is to clear status indicators triggered by
PreToolUse(fired beforePermissionRequest) when I explicitly deny with a reason (allowing the agent loop to continue) andPostToolUsenever fires.+1. The lack of
tool_use_idon PermissionRequest makes it impossible to correlate permissions with tool usage.When a PermissionRequest fires, the hook payload includes the tool name and input but not the
tool_use_id. The only workaround is positional matching — the permission event sits between the tool's PreToolUse and PostToolUse for the same tool name — which breaks for parallel calls to the same tool in one turn.The paired PermissionRequest/PermissionResponse design here would solve this cleanly:
tool_use_id— direct link to the tool call being gatedrequest_id— pairs the request to its response, even with out-of-order eventsAlso +1 on #37153's
ToolUseDeniedidea — a denied tool currently produces a PreToolUse with no matching PostToolUse, and consumers can't distinguish a denial from an interrupt or a crash.I have another use case, where a paired hook is needed for PermissionRequest
Thus: