[FEATURE] Hook event when a permission request is resolved (PermissionResult)

Status Open
Maintainer reply None cached
Activity 1 comment · opened Jul 21, 2026

Summary

PermissionRequest hooks fire when a permission dialog is about to be shown, and can decide it. But **no event fires when a permission request is resolved** — whether by the local dialog, by a hook, or by Remote Control. External tools that mirror or relay permission prompts (remote-approval tools, audit loggers, notification bridges) have no timely way to learn that a prompt they surfaced was answered elsewhere.

Use cases

  • Remote-approval tools: a growing category (e.g. cc-remote-approval, claude-remote-approver, and similar hook-based tools; I'm building one) mirrors permission prompts to another surface. When the user answers the local dialog instead, the mirrored item goes stale with no signal to clear it.
  • Audit logging: record who/what/when/which decision for every permission — currently impossible to capture the decision moment or the decision source.
  • Metrics: time-to-decision, approve/deny rates per tool.

Current behavior (measured on v2.1.211, macOS)

With a long-running PermissionRequest hook registered (blocking, waiting for a remote decision):

  1. The local dialog is shown concurrently with the running hook (first responder wins — good!).
  2. When the user answers the local dialog, the still-running hook is not cancelled (observed alive 50+ seconds after the dialog was answered, until it timed out on its own).
  3. The transcript offers no help: the tool_use entry is written at request time (before approval — same second the hook starts), and the tool_result at completion time. The approval instant itself is recorded nowhere.

Measured example (timestamps from one session):

00:42:28  assistant tool_use  (= the moment PermissionRequest fired; before approval)
~00:42:33  user answers the local dialog — no observable record
00:43:18  user tool_result   (= command finished; first observable signal)

Workaround today, and why it's insufficient

We watch the session transcript (JSONL) incrementally and treat the appearance of a matching tool_result as "resolved elsewhere". It works, but the clear latency equals the tool's execution time — for a long build or test run, a prompt that was answered seconds after appearing stays "pending" on the remote surface for minutes. Deny is fast (the rejection tool_result is immediate); approve of anything long-running is the unfixable case.

Proposal

An observation-only hook event, fired whenever a permission request is resolved, regardless of source:

{
  "hook_event_name": "PermissionResult",
  "session_id": "...",
  "cwd": "...",
  "tool_name": "Bash",
  "tool_input": { "command": "..." },
  "decision": { "behavior": "allow" }
}

Nice-to-haves: decision.source (dialog / hook / remote / always_allow_rule), and a correlation id (tool_use_id or prompt_id) so external tools don't need input-equality matching.

A weaker alternative that would also solve the cleanup case: terminate still-running PermissionRequest hooks once the request is resolved by another responder — hook-process termination is already detectable by the tool that spawned the wait.

Prior art

The hook vocabulary Claude Code established is becoming an ecosystem standard: OpenAI's Codex CLI adopted PermissionRequest with the same hookSpecificOutput.decision schema, and Kimi Code ships both PermissionRequest and PermissionResult (observation-only) events. PermissionResult is the missing complement in Claude Code's own contract.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗