Hook event 08 of 33

PermissionRequest

Runs when Claude Code is about to ask for permission or would auto-deny a call in a session that cannot prompt.

Exit code veto
Exit code cannot block. Exit code has no vote at all; the decision runs entirely through a JSON decision object, on any exit code.
Matchers
Tool names, exact or regex
if filter
Applies
Docs checked
2026-08-29

Only the calls that would have asked

You already know PreToolUse sees every tool call. PermissionRequest is narrower: it sees the subset Claude Code was about to ask you about, or would auto-deny in a session with nobody there to ask. Sandbox network prompts do not fire it. The matcher filters tool names and the if filter applies, both as on PreToolUse.

The payload carries suggestions

Before you answer, look at what Claude Code was going to offer. Alongside permission_mode, tool_name, and the tool’s own tool_input, the payload may carry permission_suggestions: the always-allow rules Claude Code would have offered you at the prompt. Each entry can hold type, rules, behavior, destination, or whatever its update type requires. There is no tool_use_id on this event.

The decision is a JSON object

Your hook speaks through JSON, not through exit codes. The answer lives under hookSpecificOutput.decision, with behavior set to allow or deny. An allow can carry updatedInput and updatedPermissions. A deny can carry message and interrupt. Permission updates support addRules, replaceRules, removeRules, setMode, addDirectories, and removeDirectories, each with the destination the hooks reference documents for it. So a hook here can answer the prompt, change the arguments, and rewrite the rules for next time, in one object.

The exit code has no vote

On the other blocking events, exit 2 is the strong answer. Here it is nothing. Exit 2 is not honoured, its stderr is discarded, and the prompt appears as if the hook had not run. Exit 0 with no JSON leaves the prompt in place too. Exit 1 without JSON is the same prompt, plus a hook error in the transcript.

What changes the outcome is a valid JSON decision object on stdout, and the reference says that is read whatever the exit code was. PreToolUse blocks with exit 2 first and JSON second. PermissionRequest reads JSON only.

The mistake most people make first

You copy block_destructive.py from module 09 to this event, since it already refuses the right commands. It exits 2 with a stderr sentence.

The permission prompt appears anyway. Your sentence appears nowhere. If you answer yes at the prompt, the command runs. Exit 2 is ignored on this event and stderr is discarded with it, so the copy did nothing and reported nothing.

Rewrite the refusal as a hookSpecificOutput.decision object with behavior: "deny" and your sentence in message, and exit 0. On this event the JSON is the only thing read.

Module 11 covers the rules this event can rewrite through updatedPermissions.