PermissionRequest hook is awaited before the local dialog for background subagents; the main session runs them in parallel
Summary
For the main session, a PermissionRequest command hook runs concurrently with the local permission dialog — both surfaces are live and the first answer wins. For background subagents, the hook is awaited before the dialog is constructed, so a hook that holds (for example to route the approval to a phone or a chat channel) removes the local prompt for the entire hold.
Both halves of the subagent path work as documented: the hook fires and carries agent_id, and a returned hookSpecificOutput.decision of allow is honored. The only asymmetry is the ordering.
Environment
- Claude Code 2.1.220, Linux x86_64, interactive TTY
PermissionRequestcommand hook registered from a plugin'shooks.json,matcher: "*",timeout: 86400- The hook forwards the event to a local daemon over a unix socket and does not write stdout until an answer arrives from elsewhere. "Holding" below means exactly that: the hook process is alive and has produced no output yet.
- All timings are from daemon-side logs that record each hook dispatch and each
Notificationof typepermission_prompt.
Measurement 1 — main session: dialog is present while the hook holds
- The
permission_promptNotification fired 6.0s after the request in 5 of 5 holds (spread 5.992s–6.008s). - 4 requests were answered at the keyboard while the hook was still holding; the hook's later answer was discarded, which is the expected outcome of a first-answer-wins race.
- Two holds shorter than 6s produced no Notification at all, which is consistent with that notification having its own delay rather than with the dialog being absent.
So for the main session both surfaces coexist, and either one can settle the request.
Measurement 2 — background subagent: no dialog until the hook answers
Same hook, same session, request originating from a backgrounded subagent:
T+0.000s PermissionRequest hook invoked (agent_id present, tool_name Read)
... hook holds; configured window = 120s
zero permission_prompt Notifications for the entire hold
T+120.0s hook returns {} (window expired, no decision)
T+126.079s permission_prompt Notification → the dialog is constructed only now
The +6.079s offset after the pass-through matches the notification delay measured above, so the dialog appeared essentially the moment the hook answered — not before.
Measurement 3 — the decision path itself is fine
Same setup, but answered remotely instead of timing out:
T+0.000s PermissionRequest hook invoked (agent_id a3a3…, tool_name Read)
T+26.000s hook returns hookSpecificOutput.decision = { "behavior": "allow" }
T+26.080s PostToolUse for the same agent_id + tool_name → the tool executed
zero permission_prompt Notifications at any point
So allow is honored for subagents, and it correctly suppresses the prompt. Nothing is broken here; the report is only about when the dialog is built.
For what it is worth, the behavior looks like it comes from a flag set on a spawned agent's permission context when that agent is async, which turns the hook into an await ahead of dialog construction. The main-session path instead starts the hook as a background task that races the dialog, with a shared claim latch deciding the winner.
Why it matters
Any integration that routes approvals to another surface has to choose, for subagents only, between being able to approve remotely and a local prompt existing at all:
- Hold the request, and someone sitting at the keyboard cannot answer it — there is nothing on screen for the duration of the window.
- Pass it through, and the request becomes unanswerable from anywhere except the keyboard, because the hook invocation is over.
The main session gets both surfaces for free. Since 2.1.198 subagents run in the background by default, so this applies to most subagent tool calls.
Request
Run the PermissionRequest hook concurrently with dialog construction for these agents too, with first-answer-wins — the same arrangement the main-session path already uses.
Related
- #23983 — hook never fires for Agent Teams teammates. A different failure: absence, not ordering.
- #79177 — reports (its Mode A) that a subagent's returned
allowwas ignored. Not reproducible here on 2.1.220 when the decision uses the documentedhookSpecificOutputenvelope; see Measurement 3.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗