[FEATURE] PermissionRequest hook input should include tool_use_id

Status Open
Reported on v2.1.221
Maintainer reply None cached
Activity 0 comments · opened Aug 4, 2026

Preflight Checklist

  • [x] I have searched existing requests
  • [x] This is a single feature request

Re-filing per the bot's note on #13938 ("Please file a new issue and reference this one if it's relevant"). That issue asked for the same field and was auto-closed as stale in March 2026, not declined on merit.

Worth noting the two requests come from different directions. #13938 wants the id so an external approver and the in-app approver can be reconciled — "it is impossible to handle the case where an external tool is configured to resolve a PermissionRequest, but the user resolves it in Claude Code itself". Mine is a status-reporting consumer that needs to know which call an approval unblocked. Same missing field, unrelated use cases.

This report adds a measured payload capture on 2.1.221, the changelog history that explains how the gap appeared, and a downstream failure it causes.

Problem Statement

PermissionRequest is the only tool-lifecycle hook that cannot be tied to the tool call it belongs to.

Measured on 2.1.221 by tee-ing the hook's stdin to a file, 8 captures across two sessions and both default and acceptEdits modes. Every one carried exactly these top-level keys:

session_id · transcript_path · cwd · prompt_id · permission_mode ·
effort · hook_event_name · tool_name · tool_input · permission_suggestions

tool_use_id appeared in 0 of 8. A representative capture:

{
  "session_id": "fe59429f-…",
  "transcript_path": "/Users/…/….jsonl",
  "cwd": "/Users/…",
  "prompt_id": "96d41930-…",
  "permission_mode": "default",
  "effort": { "level": "high" },
  "hook_event_name": "PermissionRequest",
  "tool_name": "Bash",
  "tool_input": {
    "command": "ls -l /tmp/probe-test.txt 2>&1; chmod 644 /tmp/probe-test.txt && …",
    "description": "Check and chmod probe-test.txt to 644"
  },
  "permission_suggestions": [ … ]
}

PreToolUse and PostToolUse both carry tool_use_id, so a hook can pair a start with its completion — but it cannot pair either with the approval that gated them.

The changelog suggests this is an ordering accident rather than a decision:

  • 2.0.43 — "Added tool_use_id field to PreToolUseHookInput and PostToolUseHookInput types"
  • 2.0.45 — "Added PermissionRequest hook to automatically approve or deny tool permission requests with custom logic"

The field landed two releases before the event existed, and no later entry adds it to the permission input.

prompt_id does not close the gap: it identifies the user turn, and a single turn routinely contains several tool calls — including several calls to the same tool with similar or identical inputs.

Proposed Solution

Include tool_use_id in the PermissionRequest hook input, matching the value the same call reports in PreToolUse and PostToolUse.

 {
   "hook_event_name": "PermissionRequest",
   "tool_name": "Bash",
   "tool_input": { "command": "…" },
+  "tool_use_id": "toolu_01ABC…",
   "permission_suggestions": [ … ]
 }

Same field name and value shape as the other two tool events, so a hook that already reads tool_use_id needs no new parsing. (PermissionDenied would benefit from the same treatment, but this request is scoped to PermissionRequest.)

Alternative Solutions

Correlating on (tool_name, tool_input) is the obvious workaround and it is not sound:

  • Claude batches tool calls, so two calls of the same tool can be in flight at once with identical or indistinguishable inputs.
  • Hook delivery is per-event HTTP from a shell script; delay, loss, duplication and reordering are all reachable, so "the most recently announced call" is not reliably the one being gated.
  • The consumer cannot tell "this announcement is the call the prompt is about" from "this announcement merely looks like it".

I tried it and it did not hold up. Orca is an agent workspace that turns these hooks into "this pane needs your approval" indicators; it has an open bug where the indicator stays stuck after the user approves, because the approval cannot be matched to the call that resumes (stablyai/orca#11644, filed by me). I then submitted a PR pairing prompts with announced calls through a bounded evidence ledger (stablyai/orca#11711, also mine).

The part worth citing is not that it was rejected but how. An Orca maintainer ran two independent adversarial reviews and found the ledger could clear an indicator while a different call's dialog was still open — the failure mode that hides a prompt still waiting on the user. Their conclusion, in closing it:

This cannot be repaired safely with another timing or uniqueness heuristic because the permission event does not carry provider-authored call identity.

That is a maintainer of a consuming project independently reaching the same place after reviewing a real implementation, not a hypothetical.

So the workaround space is not merely inconvenient; the safe options are to either leave the wrong state on screen or risk hiding a live prompt.

Priority

High - Significant impact on productivity

Feature Category

Developer tools/SDK

Use Case Example

Reproducing the capture takes three lines. Add a second PermissionRequest hook alongside any existing one:

{ "hooks": { "PermissionRequest": [
  { "matcher": "*", "hooks": [ { "type": "command", "command": "/tmp/capture.sh" } ] }
] } }
#!/bin/sh
cat >> /tmp/permission-payload.log
printf '\n---\n' >> /tmp/permission-payload.log

Then ask Claude for any command that needs approval and read the log.

The workflow this blocks: a hook consumer wants to show "this pane is waiting for you" and clear it the moment the approved call actually runs. With tool_use_id on the permission event, that is a single equality check against the PreToolUse/PostToolUse id — no timing heuristics, no guessing between look-alike siblings, and no risk of clearing an indicator for a prompt the user has not answered.

View original on GitHub ↗