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 Okonkwo6 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:

~/.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.

done2m ago

Claude Code finished: refactor auth middleware

Idle prompt returned. 4 files changed, 2 tests added, npm test passed. This is what a Stop event should carry — not just "it stopped," but enough of the diff to decide whether you need to look now or later.

blocked9m ago

Claude Code wants to run: rm -rf dist/

Permission request, waiting on y/n. This is what a Notification event should carry — the exact thing it's asking permission for, not just "Claude needs your attention."

A notification that just says "Claude Code needs input" makes you tab over to find out what. A useful one answers the question in the banner itself.

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 — a webhook to a service like ntfy or Pushover, or a tool that already watches the pane for you.

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
Pane-aware pushmtmux watch --agentsyou're anywhere, on your phonenpm i -g mtmux

That last row is the honest gap in the hooks approach: Stop and Notification are Claude-Code specific, so if you also run Codex CLI or aider you're writing and maintaining a separate wrapper per tool. mtmux attaches to the tmux server your agents already run in and applies a preset per agent — Claude Code, Codex CLI, Gemini CLI, aider — so one setup covers all of them instead of four hook configs that drift out of sync.

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.

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 is only relevant once you want the same notification behaviour across multiple agent tools, or you want it to reach your phone instead of your desktop.

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

Related reading

agents

Stop babysitting your AI coding agent

A long agent run fails in four distinct ways and only one of them is success — here's why exit codes can't tell them apart, and what actually can.

Nadia Okonkwo7 min read