[FEATURE] PermissionRequest hook input should include tool_use_id (per-call id) to correlate identical simultaneous prompts

Status Open
Reported on v2.1.273
Maintainer reply None cached
Activity 0 comments · opened Sep 17, 2026

Preflight Checklist

  • [x] I have searched existing requests. #13938 and #83748 asked for the same field and were both auto-closed as stale, not declined; the bot asked for a new issue that references them.
  • [x] This is a single feature request (not multiple features)

Problem Statement

PermissionRequest is the only tool-call hook event whose input cannot be tied to the call it gates. PreToolUse, PostToolUse, PostToolUseFailure, PermissionDenied and PostToolBatch all carry tool_use_id. The hooks reference still says PermissionRequest receives tool_name and tool_input "like PreToolUse hooks, but without tool_use_id".

This breaks external permission brokers that mirror a pending approval to another surface (a web or mobile card) and must retire that card when the user answers in the terminal instead. The broker learns about the terminal answer from the transcript (tool_use.id / tool_result.tool_use_id), but it has no identifier linking that id to the PermissionRequest it parked.

The unrecoverable case is two simultaneous calls with identical input (for example the same no-argument tool, or the same Bash command twice in one turn):

  • Their PermissionRequest payloads have the same session_id, tool_name, tool_input and prompt_id, so nothing in the payload tells them apart.
  • prompt_id identifies the user prompt, not the individual tool call, so it cannot separate calls made in the same turn.
  • Pairing PermissionRequest with PreToolUse by session, prompt, tool name and input fails for exactly this case. Pairing by arrival order is a guess.

Evidence (measured on Claude Code 2.1.273, Linux, isolated settings via --settings and --setting-sources ""): a hook teed stdin for both events.

  • PermissionRequest top-level keys: session_id, transcript_path, cwd, scratchpad_dir, prompt_id, permission_mode, effort, hook_event_name, tool_name, tool_input, permission_suggestions. There is no tool_use_id.
  • PreToolUse top-level keys: the same set without permission_suggestions, plus tool_use_id.
  • This matches the 2.1.221 capture in #83748, so the gap has persisted across releases. We have not re-captured on builds after 2.1.273; the current hooks reference still documents the omission.

Impact: a correct broker has to fail closed. When it cannot prove which parked request a terminal answer resolved, it resolves neither. Both remote cards stay pending until the hook times out (for us, 30 minutes for unattended sessions and up to 24 hours otherwise), even though the user already answered. The alternative, guessing, can clear the card for the call that is still waiting and leave the answered one pending. We will not ship that.

Proposed Solution

Add tool_use_id to the PermissionRequest hook input. It should be the same value that later appears as tool_use.id in the transcript and as tool_use_id in PreToolUse/PostToolUse for that call.

If the id cannot exist yet when the prompt is raised, a stable per-call identifier that is deterministically correlated with the later tool_use_id would also work, as long as the correlation is exposed (for example, the same identifier also appears on PreToolUse/PostToolUse). It must be unique per call, including for identical inputs within one prompt.

Alternative Solutions

  • Matching on session_id + tool_name + normalized tool_input (the hashing workaround mentioned on #13938) is ambiguous by construction for identical calls.
  • Pairing with PreToolUse by prompt_id and input has the same ambiguity; pairing by order is non-deterministic.
  • A "resolved elsewhere" notification to the waiting PermissionRequest hook process (suggested on #13938) would also solve it, if it identifies the call.
  • Today we fail closed and wait for the hook timeout.

Priority

High - Significant impact on productivity

Feature Category

Developer tools/SDK

Use Case Example

  1. An agent runs in Claude Code with a PermissionRequest hook that forwards approvals to a remote approval card, so a user away from the terminal can answer.
  2. In one turn the agent issues two identical calls that both need approval. Two PermissionRequest hooks fire with indistinguishable payloads, and two cards are shown.
  3. The user is at the terminal and approves one prompt there. The transcript records a tool_result for one tool_use_id.
  4. With tool_use_id on PermissionRequest, the broker retires exactly the matching card, and the other stays correctly pending. Without it, the broker cannot know which card to retire, so both stay stale until timeout.

Additional Context

  • Related (stale-closed, same field): #13938, #83748.
  • Measured version: 2.1.273. The capture payloads contained only session, path and command metadata; raw identifiers are omitted here.

View original on GitHub ↗