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.
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:
{
"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:
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.
| Tier | Setup | Reaches you when... | Needs |
|---|---|---|---|
| Terminal bell | one line in settings.json | you're at the keyboard | an unmuted bell |
| Desktop notification | osascript / notify-send | you're in the building | unlocked screen |
| Webhook push | curl to ntfy/Pushover in the hook | you have signal, any device | an account with that service |
| Open the session yourself | npm i -g mtmux, scan the code once | never — you go to it, from anywhere | Node 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.