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:
{
"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.
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.
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.
| 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 |
| Pane-aware push | mtmux watch --agents | you're anywhere, on your phone | npm 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.