claude-code-notifier
how it works guides faq
◐English
English العربية Deutsch Español Français עברית Italiano 日本語 한국어 Nederlands Polski Português (Brasil) Русский Türkçe Tiếng Việt 简体中文 繁體中文
Get started
All guides

Answering permission requests from a hook (PermissionRequest)

Updated 31 October 2026 · 6 min read
on this page 01PermissionRequest compared with PreToolUse and Notification 02What the hook receives 03The decision object 04Example: auto-approve one prompt 05Example: decide by looking at the command 06Example: allow and remember for the session 07Often a permission rule is the better tool 08Things that catch people out 09Where Claude Code Notifier fits 10FAQ

The Claude Code PermissionRequest hook runs at the moment Claude Code is about to show you a permission prompt, and it can answer that prompt for you. To allow, the hook exits 0 and prints {"hookSpecificOutput": {"hookEventName": "PermissionRequest", "decision": {"behavior": "allow"}}}; to deny, it uses "behavior": "deny" with a message for Claude. It matches on tool name, it cannot override your deny rules, and exit code 2 does nothing on this event. If the hook prints no decision, the normal prompt appears.

01PermissionRequest compared with PreToolUse and Notification

Three hook events are involved with permissions, and they do different jobs:

EventWhen it runsCan it decide?
PreToolUseBefore every tool call, whether it needs permission or notYes: allow, deny, ask or defer
PermissionRequestOnly when Claude Code is about to ask youYes: allow or deny
Notification (permission_prompt)After a prompt has waited about six seconds while you're awayNo

Use PermissionRequest when you care about the prompts themselves: answering them, logging them, or sending them somewhere. It fires straight away, with no six-second delay. Use PreToolUse for rules that should apply to every call, such as "never run rm -rf", because it runs even when no prompt would appear. The Notification hook guide explains that event's timing.

02What the hook receives

Along with the common fields, a PermissionRequest hook gets the tool_name and tool_input of the call Claude wants to make, and an optional permission_suggestions array:

{
  "session_id": "abc123",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PermissionRequest",
  "tool_name": "Bash",
  "tool_input": {
    "command": "rm -rf node_modules",
    "description": "Remove node_modules directory"
  },
  "permission_suggestions": [
    {
      "type": "addRules",
      "rules": [{ "toolName": "Bash", "ruleContent": "rm -rf node_modules" }],
      "behavior": "allow",
      "destination": "localSettings"
    }
  ]
}

permission_suggestions holds the rule changes Claude Code would suggest for this request. It is not an exact copy of the options in the dialog, because each dialog builds its own options.

03The decision object

Return a decision inside hookSpecificOutput:

FieldUse
behavior"allow" or "deny"
updatedInputAllow only. Replaces the tool's whole input, so include unchanged fields too
updatedPermissionsAllow only. Permission changes to apply, such as adding a rule or changing mode
messageDeny only. Tells Claude why
interruptDeny only. true also stops Claude

Two rules to remember:

  • Deny and ask rules still apply. A hook returning "allow" doesn't override a matching deny rule in your settings, and modified input is checked against those rules again.
  • Exit code 2 is ignored here. On most events exit 2 blocks, but a PermissionRequest hook that exits 2 without a decision object leaves the prompt as it was. To deny, return the decision.

04Example: auto-approve one prompt

Anthropic's own example approves ExitPlanMode, the prompt that appears when Claude has finished a plan, so you aren't asked every time:

{
  "hooks": {
    "PermissionRequest": [
      {
        "matcher": "ExitPlanMode",
        "hooks": [
          {
            "type": "command",
            "command": "echo '{\"hookSpecificOutput\": {\"hookEventName\": \"PermissionRequest\", \"decision\": {\"behavior\": \"allow\"}}}'"
          }
        ]
      }
    ]
  }
}

Keep matchers narrow. An empty matcher or .* with a blanket allow would approve every prompt, file writes and shell commands included.

05Example: decide by looking at the command

This script (needs jq) denies anything that touches git push with a reason Claude can read, and leaves every other prompt to you by printing nothing:

#!/bin/bash
# ~/.claude/hooks/permission-check.sh
input=$(cat)
cmd=$(jq -r '.tool_input.command // empty' <<<"$input")

case "$cmd" in
  *"git push"*)
    jq -nc '{hookSpecificOutput: {hookEventName: "PermissionRequest",
             decision: {behavior: "deny",
                        message: "Pushing is not allowed from this session. Commit and stop."}}}'
    ;;
esac
exit 0
{
  "hooks": {
    "PermissionRequest": [
      {
        "matcher": "Bash",
        "hooks": [{ "type": "command", "command": "~/.claude/hooks/permission-check.sh" }]
      }
    ]
  }
}

Build the JSON with jq rather than by joining strings, so quotes in the message can't break it. And make sure nothing else prints to stdout. If your shell profile echoes a line, Claude Code won't read the decision.

06Example: allow and remember for the session

To approve a request and stop Claude asking again until the session ends, add an updatedPermissions entry with destination: "session". A hook can echo one of the permission_suggestions it received, changing only the destination:

#!/bin/bash
input=$(cat)
jq -c '{hookSpecificOutput: {hookEventName: "PermissionRequest",
        decision: {behavior: "allow",
                   updatedPermissions: [(.permission_suggestions // [])[]
                                        | select(.type == "addRules")
                                        | .destination = "session"]}}}' <<<"$input"

As written, this allows every request it sees, so pair it with a narrow matcher. The destination can also be localSettings, projectSettings or userSettings, which write the rule to a settings file instead of keeping it in memory.

07Often a permission rule is the better tool

If the answer never changes, you don't need a hook. Put the rule in settings.json and Claude Code applies it without running anything:

{
  "permissions": {
    "allow": ["Bash(npm run *)"],
    "deny": ["Bash(git push *)"]
  }
}

Rules are evaluated deny first, then ask, then allow. A hook earns its place when the decision depends on something a rule can't see, such as the time of day, the branch you're on, or a person answering from somewhere else. Permission modes explained covers the other built-in options.

08Things that catch people out

  • Non-interactive runs. With claude -p, there is no one to prompt. PermissionRequest hooks still run, and if none returns a decision the call is denied.
  • Timeouts. A command hook can run for up to 600 seconds by default, and Claude waits. Set timeout (in seconds) if your hook waits on something slow, such as a person.
  • Some prompts don't trigger it. A sandboxed command's network request doesn't run PermissionRequest. Use the permission_prompt notification type for that.
  • Security. A hook that returns "allow" is approving actions in your name with your user's permissions. Review it like any other code that can run commands.

09Where Claude Code Notifier fits

The most common reason to write a PermissionRequest hook is to answer prompts when you're not at the keyboard. A DIY version needs the hook to send the request somewhere, wait for an answer, and return it before the timeout. That is doable, and some people build it with a chat bot.

Claude Code Notifier does this for you. Its free command line tool, ccn, runs on Mac, Windows and Linux and installs its hooks into ~/.claude/settings.json. With the iPhone or Android app paired, a permission request arrives on the phone and you can allow once, allow for the session, or deny; ccn returns that decision from the PermissionRequest hook. If you typed in the last 30 seconds, nothing waits and the terminal asks as usual. If you are away, the terminal waits for the phone up to a ceiling you choose (hold_ceiling_seconds, default 600), and stops waiting the moment you touch the keyboard. It is independent and not made by Anthropic. See approving permissions from your phone for the workflow and is remote approval safe? for the security side.

10FAQ

How do I auto-approve permissions with a hook?

Add a PermissionRequest hook with a narrow matcher, and have it print a JSON decision with "behavior": "allow" inside hookSpecificOutput, then exit 0. If the same command should always be allowed, a permissions.allow rule in settings.json is simpler and doesn't need a hook.

Why doesn't exit code 2 deny the request?

PermissionRequest is one of the events where exit 2 isn't honoured. Claude Code ignores it and shows the normal prompt. Return "behavior": "deny" in the decision object instead.

Can a PermissionRequest hook override my deny rules?

No. Deny and ask rules are still evaluated, so an "allow" from a hook doesn't get past a matching deny rule. Any updatedInput you return is checked against those rules too.

What is the difference between PermissionRequest and PreToolUse?

PreToolUse runs before every tool call and can allow, deny, ask or defer. PermissionRequest runs only when Claude Code is about to show a prompt, and can allow or deny it. Use PreToolUse for policy, PermissionRequest for answering prompts.

What happens if my hook prints nothing?

Nothing changes. Claude Code shows the usual permission prompt in the terminal, as if the hook weren't there.

Claude Code Notifier Get a push on your phone when Claude Code finishes or needs you, then approve, deny or answer from there. The ccn command line tool is free on Mac, Windows and Linux.
Download for iOS Download for Android Install ccn

Related guides

  • Approve Claude Code permission requests from your phoneClaude Code's PermissionRequest hook can return allow or deny. Here is how to answer permission prompts from your phone, by hand or with ccn.
  • Claude Code hooks explained: every event and what it is forWhat Claude Code hooks are, where they go in settings.json, how matchers, input and exit codes work, and what each hook event is for.
  • Claude Code permission modes explained (default, acceptEdits, plan, bypassPermissions)What each Claude Code permission mode does (default, acceptEdits, plan, auto, dontAsk, bypassPermissions), how to switch, and when each is safe.
  • Is it safe to approve Claude Code actions from your phone?How remote approval for Claude Code works, what can go wrong, what data leaves your computer, and the settings that keep it safe.
claude-code-notifier

Claude Code Notifier is an independent third-party application. It is not affiliated with, endorsed by, or sponsored by Anthropic, PBC. "Claude" and "Claude Code" are trademarks of Anthropic, PBC.

apps iOS App Android App ccn for macOS, Windows & Linux All guides
legal Privacy Policy Terms of Service Other languages © 2026 touchtap. All rights reserved. TouchTap Ltd is registered in England and Wales, company number 13942589.