[FEATURE] Hook event when a permission request is resolved (PermissionResult)
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):
- The local dialog is shown concurrently with the running hook (first responder wins — good!).
- 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).
- The transcript offers no help: the
tool_useentry is written at request time (before approval — same second the hook starts), and thetool_resultat 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.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗