PermissionRequest hooks: subagent prompts don't invoke the hook; when it did fire, a returned allow was ignored
Environment
- claude-code 2.1.191 (Linux x86_64, Arch package;
DISABLE_AUTOUPDATER=1) - Hook registered via a plugin's hooks.json under
PermissionRequest(matcher covers Read); the same plugin registers a PreToolUse hook, which is how we can prove plumbing liveness below. - Settings context: no allow/deny/ask permission rules for Read configured, so the documented tighten-only caveats have nothing to bite on.
Documented behavior (docs.claude.com/claude-code hooks reference, fetched 2026-07-17)
- PermissionRequest: "Returning
allowskips the interactive prompt but doesn't override permission rules." - "Permission requests generated by subagent tool calls fire
PermissionRequesthooks…" (payload documented to carryagent_id/agent_type).
We observed two distinct failure modes, both contradicting the above.
Mode A — hook fires, returned allow is ignored (observed 2026-07-17 ~05:24Z)
A subagent's Read of a world-readable file outside the workspace triggered the built-in out-of-directory permission flow. Our PermissionRequest hook was invoked (payload carried tool_name: Read, the file path, and the subagent identity) and answered within ~1 ms:
{"decision": {"behavior": "allow", "updatedInput": {"file_path": "…"}}}
The interactive prompt was shown to the human anyway. We initially suspected our output shape (top-level decision vs the documented hookSpecificOutput envelope) and fixed it to emit the documented envelope — which brings us to mode B, because we could never verify the envelope fix:
Mode B — the hook is not invoked at all for a subagent's prompt (observed 2026-07-17 ~13:42Z)
A subagent Read of a never-before-approved world-readable path (/etc/os-release) produced the interactive prompt, and our PermissionRequest hook was never invoked — while our PreToolUse hook for the same tool call fired 5 ms earlier (both hooks in the same plugin, same daemon socket — plumbing demonstrably alive). Our hook-side event log records the PreToolUse dispatch and zero PermissionRequest dispatches for that session.
This directly contradicts documented subagent coverage (quote 2). Whether A-vs-B selection is version drift or depends on subagent type / permission mode (we saw A with a ticket-worker-type agent under acceptEdits, B with an Explore-type under default) we cannot determine from outside.
Workaround that works (for triagers' reference)
A PreToolUse hook returning hookSpecificOutput.permissionDecision: "allow" suppresses the out-of-directory prompt for both main-agent and subagent Reads on this build (live-verified at the keyboard, zero prompts). We have moved our read policy to that seat.
Questions
- Is PermissionRequest expected to fire for subagent tool-call prompts on current builds?
- When it does fire and the hook returns a documented allow (enveloped), is that expected to suppress the built-in out-of-directory ask when no permission rules are configured?
Happy to provide the full timestamped hook-event log for both modes.
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
Data points on your two questions, from 2.1.220 (Linux x86_64, interactive TTY), in case they help triage. My hook is a
PermissionRequestcommand hook registered from a plugin'shooks.jsonwithmatcher: "*", and I have daemon-side logs that timestamp every dispatch.Q1 — does
PermissionRequestfire for subagent tool-call prompts?Yes on this build, for backgrounded subagents (the default for the
Agenttool since 2.1.198). The payload carriesagent_idandtool_name; I have ~10 such dispatches logged today, e.g.agent_id: a3a3a4f8fd49bb54a, tool_name: Read.Q2 — is a returned
allowhonored?Yes, when the decision uses the documented
hookSpecificOutputenvelope. Measured on one subagentRead:So your Mode A may well have been the top-level-
decisionshape you already suspected — I could not reproduce it with the enveloped form. If you can re-test the envelope fix, that would settle it.One diagnostic note that cost me some time and may be relevant to how Mode B was observed: when a hook holds a subagent's request, the hold is invisible from inside the session. No dialog appears and the tool simply takes longer and then succeeds, which reads exactly like "the call was never gated". I misread it that way myself before checking the hook-side log. So "no prompt appeared" is not by itself evidence that the hook did not fire.
Separately, I filed #82150 for an adjacent asymmetry I did reproduce: while the hook is holding, no local dialog is constructed for a backgrounded subagent (measured: zero
permission_promptfor a 120s hold, then the dialog appearing 6.079s after the hook returned{}), whereas the main session constructs it in parallel and lets the first answer win.Thanks for the detailed report. I tried to reproduce this on 2.1.233 on Linux, and also on your exact version 2.1.191, using your suggested repro: a
PermissionRequesthook (matcherRead) insettings.jsonthat logs stdin and prints{"hookSpecificOutput":{"hookEventName":"PermissionRequest","decision":{"behavior":"allow"}}}, plus the same hooks registered via a plugin'shooks.json, reading/etc/hostnamein a default-mode session in tmux.In all four combinations (both versions × settings.json/plugin), both modes behaved as documented: the hook fired and the prompt was skipped ("Allowed by PermissionRequest hook"), and a subagent's Read also fired the hook — with
agent_id/agent_typein the payload — and the allow was honored. So I can't reproduce either mode as described.Two things that can look exactly like what you saw:
allow. If the hook script is slow or doesn't exit (e.g. a background child keeps stdout open), the prompt stays on screen even though the hook "fired".Could you share your exact hook script, its raw stdout, and a
claude --debug-file /tmp/cc-debug.txtlog from a failing run (grep forPermissionRequest)? Happy to dig back in with that.🤖 Generated with Claude Code
We weren't able to reproduce this. Could you provide steps to trigger the issue — what you ran, what happened, and what you expected? This issue will be closed automatically if there's no activity within 7 days.