Skip to content

agents

Claude Code notifications: the Stop and Notification hooks

Claude Code fires Stop and Notification hooks you can wire to a sound, a desktop alert, or a push — the real config, and where each stops helping.

Nadia Okonkwoupdated Aug 10, 20268 min read

Claude Code ships two hooks that need no extra tooling: Stop, which fires when Claude finishes responding, and Notification, which fires when it is waiting on you — a permission prompt, an idle prompt, an MCP server asking for input. Wire either to a shell command in settings.json and you get a sound, a desktop banner, or anything else a command line can trigger. The config below is real and copy-pasteable. The honest part comes after: none of it reaches you once you've left the desk.

Where the hooks live

Claude Code reads hooks from settings.json at one of three scopes: ~/.claude/settings.json (every project, this machine), .claude/settings.json (this project, checked into git, shared with the team), or .claude/settings.local.json (this project, not checked in — the right place for a personal afplay command your teammates don't need to see in a diff).

Each entry matches an event name to a list of matchers, and each matcher runs one or more shell commands — the shape is documented in Anthropic's own hooks reference, which is the authority on the full event list if you want more than the two below:

~/.claude/settings.json
{
  "hooks": {
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          { "type": "command", "command": "afplay /System/Library/Sounds/Glass.aiff" }
        ]
      }
    ],
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude needs input\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

"matcher": "" matches every event of that type, which is what you want for Stop and Notification — matchers earn their keep on the tool-scoped hooks like PreToolUse, where you might only want to fire on Bash or Write.

On Linux, swap the commands for their equivalents:

Linux notification command
notify-send "Claude Code" "Finished responding"

Test a hook without waiting for a real run

Ask Claude something trivial — "what's 2+2" — and watch for the sound or banner. If nothing fires, check you edited the right settings file; a typo'd path fails silently rather than erroring.

Stop is not the same signal as Notification

The distinction is easy to gloss over and it is the one that determines what you wire to each hook.

Stop fires once, when Claude has finished producing a response and handed control back to you. That covers success, a completed refactor, an answered question, and Claude giving up and explaining why — Claude Code does not distinguish those cases through the Stop event alone. It means "come look," not "it worked."

Notification fires on the events that need you now: a tool-permission request, a plan-mode exit, an idle timeout waiting on input. Nothing else happens in that pane until you respond. If you only wire one hook, wire Notification — a Claude Code session that's blocked can sit for hours doing nothing, and a Stop hook will never fire to tell you that.

Put the answer in the banner, not behind it

"Claude Code finished" makes you tab over to find out what finished. "Finished: refactor auth middleware, 4 files changed, tests green" does not. The hook runs a shell command, so it can read the repo first — git rev-parse --abbrev-ref HEAD, git diff --stat, the last line of a log — and paste that into the notification body instead of a fixed string.

The same goes for Notification. A banner reading "Claude Code needs input" costs you exactly the context switch it was supposed to save; one carrying the command that's sitting at a y/n lets you decide from a lock screen whether it can wait until you're back.

The escalation path, and where it stops working

Terminal bell

Cheapest option: hook a command that prints \a and let your terminal emulator's bell setting do the rest. Works only if you're within earshot, and most terminals mute the bell by default now — you'll spend longer re-enabling it than the hook took to write.

Desktop notification

osascript on macOS, notify-send on Linux. Reaches you anywhere in the building, on any desktop or window — genuinely useful for a run you expect to take a few minutes while you make coffee. Still requires the desktop to be unlocked and you to be near it.

Push to your phone

The only tier that survives you leaving the house. Claude Code has no native push integration, so this means piping the hook command to something that can deliver one — typically a curl to an ntfy topic, self-hosted or on ntfy's public server, or a Pushover account. Each is one line inside the hook, and each is a separate tool with its own account and its own uptime.

TierSetupReaches you when...Needs
Terminal bellone line in settings.jsonyou're at the keyboardan unmuted bell
Desktop notificationosascript / notify-sendyou're in the buildingunlocked screen
Webhook pushcurl to ntfy/Pushover in the hookyou have signal, any devicean account with that service
Open the session yourselfnpm i -g mtmux, scan the code oncenever — you go to it, from anywhereNode 22+ and tmux

That last row is deliberately not a notification, and it's the half the others leave out. Every tier above it answers "something happened"; none of them answer the question you actually have once the banner lands, which is what the pane currently says. mtmux covers that: it serves the tmux session Claude Code is already running in to a browser, end-to-end encrypted, with nothing to port-forward — so the follow-up to a push is opening the session, not finding a laptop. The reference setup for that pairing is at docs.mtmux.com/claude-code-remote.

If the agent is running inside tmux, there is a second, entirely separate place to hang the same shell command: tmux's own set-hook, which fires on what the pane does rather than on what Claude Code reports about itself — set-hook and capture-pane are both in tmux commands. It is a different tool with different blind spots, not a replacement for the hooks above.

What a good escalation setup actually looks like

Bell for anything under a minute — you're probably still watching. Desktop notification for anything you'd otherwise poll for by tabbing back every thirty seconds. Push for anything you'd walk away from: a long refactor, a test suite you don't want to babysit, a migration you kicked off before lunch. If you're setting up hooks and stopping at desktop notifications, you've solved the "I forgot to check" problem and left the "I wasn't at my desk" problem untouched — which for long agent runs is the more common one. That's the argument made at length in stop babysitting coding agents.

If you also run OpenAI's CLI, the equivalent hook is different in shape and in what it goes silent on — see Codex CLI notifications. For the same question asked across every agent at once — which ones signal natively, and what each one stays quiet about — coding agent notifications is the survey this post is one chapter of.

The command inside the hook is a separate problem from the hook itself, and the one people get wrong: a banner you cannot hear is not much use in another room. Making Claude Code play a sound when it's done covers afplay, paplay and the PowerShell equivalent, and why Stop and Notification want two different sounds.

What's the difference between the Stop and Notification hooks in Claude Code?

Stop fires once when Claude finishes responding, for any reason. Notification fires when Claude is waiting on you mid-run — a permission prompt or an idle timeout. Wire Notification first: a blocked session produces no Stop event until you unblock it.

Do Claude Code hooks work on Linux and Windows?

Yes — the hook system itself is cross-platform; only the notification command changes. Use notify-send on Linux with a desktop environment, and a Windows Toast command or WSL's notify-send equivalent on Windows.

Can I limit a hook to specific tools, like only Bash commands?

Yes, via the matcher field on tool-scoped events like PreToolUse and PostToolUse — set it to a tool name instead of an empty string. Stop and Notification aren't tool-scoped, so their matcher is typically left empty.

Will a desktop notification wake my laptop if it's asleep?

No. A sleeping machine isn't running the hook's command in any meaningful sense until it wakes. If you need to know about a run that might outlast your laptop staying awake, the notification needs to originate from somewhere that stays reachable — a push service, not a local osascript call.

Do I need mtmux to use Claude Code's hooks?

No — the hooks above work standalone with nothing installed beyond Claude Code itself. mtmux doesn't send notifications and isn't a replacement for one; it's what you open after a banner arrives, when the machine running the session isn't reachable from the device in your hand.

Put this to work — mtmux attaches to the tmux server you already run.

Related reading