PricingSign inDownload

Blog / claude code

Get a Claude Code notification only when it needs you

Claude Code notifications that fire after every turn, or never: which types it sends, when each fires, and a hook that pings only when it needs you.

On this page
  1. What Claude Code actually sends
  2. A hook that pings only when it needs you
  3. If you also want "it's done"
  4. When it never fires
  5. Getting it to your phone
  6. Where Joxo fits
  7. Frequently asked questions
  8. Why does my Claude Code notification fire after every response?
  9. Which notification types does Claude Code send?
  10. How do I get notified when Claude Code finishes?
  11. Why don't I get any Claude Code notifications?
  12. Try it with your team

You start Claude Code on a long task, switch to something else, and want one thing: a ping when it needs you. The usual setup is a Notification hook copied from a forum post. Then it pings a minute after every reply, whether Claude asked you anything or not, so you start ignoring it. Or it never pings at all, and the session sits on "Do you want to create b.txt?" for an hour.

Both come from the same detail: Claude Code sends more than one kind of notification, and a hook without a matcher gets all of them. We logged every notification Claude Code 2.1.312 sent in a test folder to see which fires when.

What Claude Code actually sends#

We added a Notification hook with no matcher that appended the hook's input to a log file, then gave Claude Code small jobs in normal permission mode. Two kinds of notification showed up:

json
{"hook_event_name":"Notification","message":"Claude needs your permission","notification_type":"permission_prompt"}
{"hook_event_name":"Notification","message":"Claude is waiting for your input","notification_type":"idle_prompt"}

(Each line also carries the session id, transcript path and folder; trimmed here.)

  • permission_prompt fired the moment Claude Code stopped to ask. That covered the file prompt ("Do you want to create b.txt?") and a multiple-choice question Claude asked with its question tool. This is the one you want: it means "nothing happens until you answer".
  • idle_prompt fired 67 seconds after a turn ended, in a turn where Claude had asked nothing. It had written a file, said it was done, and gone quiet. The message reads "waiting for your input", but all it means is "the turn is over and nobody typed".

So a hook without a matcher pings you twice for one approval: once for the prompt, and once a minute after the job ends. Over a day of short replies, that's a ping for every reply, which is the most common complaint about Claude Code notifications.

A hook that pings only when it needs you#

Hooks take a matcher. For Notification hooks, the matcher is checked against notification_type, so you can keep the prompt and drop the idle one. This is the setup we tested, in .claude/settings.json in the project:

json
{
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code needs you\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

That command shows a macOS notification. A hook runs any command, so on another system put whatever shows a notification there, or a command that sends one to your phone.

In our run, with the same jobs as before, this hook fired once when Claude Code asked to create a file, once for the question, and not at all after the turns ended. Put it in ~/.claude/settings.json instead if you want it in every project.

If you also want "it's done"#

Waiting for idle_prompt to learn that a job finished costs you a minute each time. The Stop hook fires as soon as Claude finishes its turn:

json
{
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code needs you\" with title \"Claude Code\"'"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code finished\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

In our run the Stop hook fired right after the file was written and the approval answered, with no delay. Two things to know about it:

  • It fires at the end of every turn, so it's as chatty as idle_prompt, just faster. Use it for long jobs you walk away from, not for back-and-forth sessions.
  • A turn also ends when Claude asks you something in plain words ("Should I also update the tests?") instead of with its question tool. permission_prompt doesn't fire for that, and Stop does. If Claude often asks in text, Stop is how you hear about it.

When it never fires#

If you get no notification at all, check these in order:

  • The session isn't asking anything. In bypass permissions mode there are no prompts, so permission_prompt never comes. Our first run was in that mode by accident: Claude wrote the file straight away and the hook stayed silent. The same goes for anything already on your allow list.
  • The hook isn't loaded. Run /hooks in the session: it lists every hook Claude Code read, marked Project or User by where it came from. A hook we added while a session was running showed up and fired in our test; if yours isn't listed, check the file and folder, then start a new session.
  • The matcher is off. It's matched against notification_type, not the message text. "matcher": "permission" or "matcher": "Claude needs your permission" aren't the type names. Copy permission_prompt exactly.
  • The command fails quietly. Run it yourself first. On macOS, osascript notifications appear under Script Editor in System Settings → Notifications; if its alerts are off, nothing shows.
  • You're on a different machine than the session. A desktop notification shows on the computer running Claude Code. If that's a server or another Mac, the ping lands there.

Getting it to your phone#

The desktop notification only helps if you're at that computer. When you walk away, the useful version is on your phone, with enough context to answer: which session, what it wants to do, and a way to say yes or no without going back to the desk. A hook can send a push through a service of your choice, but it can't take your answer back to the session; you still have to return to the keyboard.

Where Joxo fits#

Joxo does this for every agent you run, not only Claude Code. A session waiting for you reaches your phone and desktop with what it's asking, you answer it there, and finished turns don't ping you. Setup is one line pasted into your agent. If the prompts themselves are the problem, see stop Claude Code asking permission for every command, safely, and for answering from anywhere, approve agent actions from your phone.

Frequently asked questions#

Why does my Claude Code notification fire after every response?#

A Notification hook without a matcher also gets idle_prompt, which Claude Code sends about a minute after any turn ends, even when it asked you nothing. Add "matcher": "permission_prompt" to keep only the notifications where it's waiting on you.

Which notification types does Claude Code send?#

In our tests on 2.1.312: permission_prompt when it stops for an approval or asks a question with its question tool, and idle_prompt about 60 seconds after a turn ends. The type is in the notification_type field of the hook's input.

How do I get notified when Claude Code finishes?#

Use a Stop hook. It fires as soon as the turn ends, without the minute's wait of idle_prompt. It fires on every turn, so it suits long jobs you walk away from.

Why don't I get any Claude Code notifications?#

Usually the session never asks (bypass permissions mode or an allow list covers it), the hook isn't loaded (check /hooks), or the matcher isn't the exact type name permission_prompt.