anotifier / guides / claude-code-permission-notifications
[ GUIDE · CLAUDE CODE PERMISSIONS ]
Claude Code logo

Get notified when Claude Code needs your permission or input

Add a Notification hook with the matcher permission_prompt to ~/.claude/settings.json. Claude Code runs it when a tool approval has waited about six seconds. The idle_prompt matcher covers the other case: Claude finished about a minute ago and you haven't typed since. Below: the exact config, every notification type Claude Code sends, one hook for everything that waits on you, the timing rules, your phone, and fixes for a hook that never fires. The Claude Code notifier wires these hooks for you.

Rather skip the manual setup? One command wires every agent you have installed to desktop toasts, phone push, and Slack, Discord or Telegram:

$ npx anotifier@latest setup

Updated 2026-10-16 · also see the Claude Code notifier and the full docs

The config: one hook per notification type

This macOS example shows a different banner for an approval and for an idle session. Each matcher gets its own entry under Notification:

{
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude needs your approval\" with title \"Claude Code\"'"
          }
        ]
      },
      {
        "matcher": "idle_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude is waiting for you\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}
PlatformCommandWatch out for
macOSosascript -e 'display notification "…" with title "Claude Code"'Banners come from Script Editor. Without its notification permission the command fails silently: run it once, then allow Script Editor in System Settings > Notifications
Linuxnotify-send 'Claude Code' 'Claude needs your approval'Needs a desktop notification daemon, which headless servers, SSH sessions and most containers don't have
WindowsPowerShell, with a module such as BurntToast for a real toastClaude Code's own example uses a MessageBox, a dialog box that can open behind your terminal

Type /hooks in Claude Code and select Notification to confirm the hooks are registered. Leave the matcher empty, or leave it out, and one hook runs for every notification type.

Every notification type Claude Code sends

MatcherFires whenWaiting on you?
permission_promptA tool approval, or a sandboxed command's network request (v2.1.246+ in a terminal), has waited about six secondsYes
idle_promptClaude finished about 60 seconds ago, you haven't typed since, and no background agent is still runningClaude is done; nothing is blocked
elicitation_dialogAn MCP server opened a form and you haven't typed for about six secondsYes
elicitation_url_dialogAn MCP server asks you to open a URL and you haven't typed for about six secondsYes
agent_needs_inputA background session waits on you while agent view is open (v2.1.198+). Also: a terminal session shows an agent team teammate's setup question (v2.1.248+) or auto mode's notice about classifier request charges, once you haven't typed for about six secondsYes
agent_completedA background session finished or failed, while agent view is open (v2.1.198+)No
quota_auto_resume_staleA usage limit reset while your computer slept, and Claude waits for Enter (v2.1.234+)Yes
auth_successAuthentication completedNo
elicitation_complete, elicitation_responseAn MCP elicitation finished, or its response was sentNo
quota_auto_resume_fired, quota_auto_resume_disabledClaude continued after a usage limit, or stopped waiting for one (v2.1.234+)No

The hook gets the event as JSON on stdin. Besides the common fields (session_id, transcript_path, cwd), a Notification hook receives message, an optional title, and notification_type, so one script can branch on the type instead of using several matchers:

{
  "session_id": "abc123",
  "transcript_path": "/Users/you/.claude/projects/…/session.jsonl",
  "cwd": "/Users/you/my-app",
  "hook_event_name": "Notification",
  "message": "Claude needs your permission",
  "title": "Permission needed",
  "notification_type": "permission_prompt"
}

One hook for everything that waits on you

A matcher can list several notification types separated by |, so one entry covers approvals and MCP questions together. Claude Code treats a matcher made only of letters, digits, _, -, spaces, , and | as an exact list, not a regular expression:

{
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt|elicitation_dialog|elicitation_url_dialog",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code is waiting for you\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

To give each type its own banner from one script, read notification_type from stdin. This Linux script needs jq and notify-send; on macOS replace each notify-send line with osascript -e 'on run argv' -e 'display notification (item 1 of argv) with title "Claude Code"' -e 'end run' "$msg", which passes the message as an argument so a quote in it cannot break the AppleScript. Save it as ~/.claude/hooks/needs-you.sh:

#!/bin/sh
input=$(cat)
type=$(printf '%s' "$input" | jq -r '.notification_type')
msg=$(printf '%s' "$input" | jq -r '.message')
case "$type" in
  permission_prompt|elicitation_dialog|elicitation_url_dialog)
    notify-send -u critical -- 'Claude Code needs you' "$msg" ;;
  idle_prompt)
    notify-send -- 'Claude Code is waiting' "$msg" ;;
esac

Register it under Notification with no matcher, so the script sees every type and ignores the ones it has no branch for, such as auth_success:

{
  "hooks": {
    "Notification": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "sh ~/.claude/hooks/needs-you.sh"
          }
        ]
      }
    ]
  }
}

Why the alert arrives seconds (or a minute) later

  • In a terminal, permission_prompt waits about six seconds after the prompt appears, so a prompt you answer right away never pings you. Each keystroke pushes that timer back.
  • In Claude Desktop and the VS Code extension, which answer permission requests through the Agent SDK, it fires about six seconds after the request and typing does not delay it. Before v2.1.233 it did not fire there at all. Set CLAUDE_CODE_DISABLE_PERMISSION_PROMPT_NOTIFY_HOOKS=1 to turn it off in those hosts.
  • `idle_prompt` comes about 60 seconds after Claude finishes, and only if you haven't typed. It is not sent while Claude waits for a usage limit to reset.
  • For an alert the moment Claude finishes, use the Stop hook, not idle_prompt. See the Claude Code notifications guide.
  • For a signal the instant Claude asks for permission, use a PermissionRequest hook. It runs before the prompt is shown and can return a decision (allow or deny); in an interactive session, a hook that returns no decision leaves the normal prompt in place.

Send approvals to your phone

Away from the desk, a banner doesn't help. ntfy is a free push service with Android and iOS apps and no account: subscribe to a topic name only you know, then POST to it from the hook. An urgent priority makes the push stand out:

{
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "curl -s -H 'Title: Claude Code' -H 'Priority: urgent' -H 'Tags: warning' -d 'Claude needs your approval' https://ntfy.sh/your-secret-topic"
          }
        ]
      }
    ]
  }
}

Anyone who knows a public ntfy.sh topic can read it, so send generic text only. The ntfy guide covers private topics and self-hosting.

The hook never fires: what to check

  1. The type is `idle_prompt`. It only runs about 60 seconds after Claude finishes, and only if you haven't typed since and no background agent, such as a background subagent, is still running. It is not sent while Claude waits for a usage limit to reset.
  2. Test the command by itself. In a POSIX shell (WSL on Windows), pipe a sample event into it: echo '{"notification_type":"permission_prompt","message":"test"}' | sh ~/.claude/hooks/needs-you.sh. If no banner appears, the problem is the command (a missing jq, notify-send or Script Editor permission), not Claude Code.
  3. Confirm the hook is registered. Type /hooks in Claude Code and select Notification. The matcher must be one of the types above, spelled exactly.
  4. You answered too fast. permission_prompt only runs after the prompt has waited about six seconds, and an answer from you before that cancels it (in Claude Desktop and VS Code, an answer from a PermissionRequest hook does too). In a terminal, each keystroke while the prompt is open pushes the timer back again.
  5. You are in Claude Desktop or the VS Code extension. They answer permission requests through the Agent SDK. The notification fires about six seconds after the request there, typing does not delay it, and before v2.1.233 it did not fire at all. Check that CLAUDE_CODE_DISABLE_PERMISSION_PROMPT_NOTIFY_HOOKS is not set to 1.
  6. Another dialog is already open: the alert can come early. A permission request that arrives behind an open dialog keeps the six-second gate, timed from when it arrives, so its notification can reach you while the request still waits in the queue. That is not a missing alert, just an early one.
  7. A sandboxed command's network request in an older terminal. permission_prompt covers it from v2.1.246. A PermissionRequest hook never runs for it, so use permission_prompt there.
  8. Desktop notifications being off is not the cause. Hooks run even when preferredNotifChannel is notifications_disabled; that setting only changes how Claude Code alerts you itself.

What anotifier does with these

npx anotifier@latest setup

Setup registers one Notification hook with an empty matcher, plus a Stop hook, in ~/.claude/settings.json. Every notification becomes a needs-input alert on your desktop, your phone (if you turn on ntfy), and your webhook, with the project in the title (my-app · Claude Code) and, in the toast and webhook, the question Claude asked.

NotificationPrioritySoundntfy tags
The idle reminder ("Claude is waiting for your input")defaultReminderhourglass_flowing_sand
Any other notificationurgentReminderbell, warning

Only the idle reminder is toned down, because nothing is blocked. To pick its level, set idleReminderPriority to min, low, default, high, or urgent:

{ "events": { "needs_input": { "idleReminderPriority": "low" } } }

Sources

The Claude Code claims on this page come from the official docs, read on 2026-10-16 against the latest Claude Code release (2.1.301). Notification types, timings and version cut-offs change between releases, so confirm anything you rely on there.

Claude Code permissions: common questions

How do I get notified when Claude Code asks for permission?

Add a Notification hook with "matcher": "permission_prompt" to ~/.claude/settings.json and give it a command that shows a banner, plays a sound, or sends a push. It runs when the prompt has waited about six seconds.

Why does the permission notification take a few seconds?

Claude Code waits about six seconds so that prompts you answer straight away never notify you. In a terminal, typing pushes the timer back. For a signal the instant Claude asks, use a PermissionRequest hook.

What is the difference between permission_prompt and PermissionRequest?

permission_prompt is a Notification type: it fires after about six seconds and can only trigger side effects. PermissionRequest is its own hook event: it runs before the prompt appears and can allow or deny the request. It does not run for a sandboxed command's network request.

Why do I get a notification a minute after Claude finishes?

That is idle_prompt: Claude Code sends it about 60 seconds after a turn ends if you haven't typed. Use a Stop hook to be told the moment Claude finishes, and leave idle_prompt out of your matchers if you don't want the reminder.

Can I approve a permission prompt from my phone?

Not with anotifier today: the push gets you back to the keyboard, and you answer in the session. A PermissionRequest hook can return allow or deny, which is what a remote-approval tool would build on.

How do I get notified when Claude Code is waiting for my response?

Use a Notification hook. permission_prompt runs when an approval has waited about six seconds, elicitation_dialog when an MCP server opens a form, and idle_prompt about 60 seconds after Claude finishes if you haven't typed. List several in one matcher with a pipe, such as permission_prompt|elicitation_dialog. For the moment a turn ends, use a Stop hook.

Can one Notification hook cover several types?

Yes. A matcher of letters, digits and pipes is an exact list, so "permission_prompt|elicitation_dialog" runs for either. Or leave the matcher out and have the script read notification_type from stdin and branch on it.

Why does my permission_prompt hook never run?

Usually you answered within about six seconds, or you are in Claude Desktop or the VS Code extension on a version before v2.1.233, or CLAUDE_CODE_DISABLE_PERMISSION_PROMPT_NOTIFY_HOOKS is set to 1. Pipe a sample event into the command to rule out the command itself, and check /hooks.

Try it

One command wires every agent you have installed: Claude Code, Codex CLI, Cursor, and Gemini CLI.

npx anotifier@latest setup

Other guides