On macOS, Claude Code already sends a desktop notification in Ghostty, Kitty and iTerm2 (iTerm2 needs its alert settings turned on), but Apple Terminal and most other terminals get nothing. For a notification in any terminal, add a Notification hook that runs osascript -e 'display notification ...' to ~/.claude/settings.json, and a Stop hook if you also want to know the moment Claude finishes. Allow notifications for Script Editor in System Settings, or the osascript command fails without a word.
01What Claude Code does on its own
Claude Code fires a notification event when it finishes a task or waits for a permission and you seem to be away from the terminal. What you see depends on the terminal, according to Anthropic's terminal guide:
For iTerm2, open Settings > Profiles > Terminal, tick Notification Center Alerts, then click Filter Alerts and turn on Send escape sequence-generated alerts. Also check that iTerm2 is allowed to send notifications in System Settings > Notifications.
In Apple Terminal, you can at least get a sound. Set "preferredNotifChannel": "terminal_bell" in ~/.claude/settings.json and turn on Audible bell under Settings > Profiles > Advanced. The terminal bell guide covers that route in detail.
The built-in alert has limits. It only fires when Claude Code thinks you are away, it only says that something happened, and it never leaves the computer.
02A notification hook with osascript
A hook works in every terminal, including IDE terminals. This is the macOS example from Anthropic's hooks guide. Add it to ~/.claude/settings.json, keeping any other keys you already have:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
}
]
}
]
}
}
Type /hooks in Claude Code to confirm it is listed under Notification.
The Script Editor permission
osascript posts notifications as Script Editor. If Script Editor has no notification permission, the command fails silently and macOS does not ask. Run this once in Terminal:
osascript -e 'display notification "test"'
Nothing appears the first time. Open System Settings > Notifications, find Script Editor, and turn on Allow Notifications. Run the command again and the test notification should show.
When it fires
The empty matcher fires on every notification type. The two you care about most are permission_prompt (Claude has waited about six seconds for an approval and you have not typed) and idle_prompt (Claude finished about 60 seconds ago and you have not typed since). The Notification hook guide lists every type and its timing.
03Know the moment Claude finishes
idle_prompt waits a minute. To be told straight away, add a Stop hook, which runs when Claude finishes its turn. Stop takes no matcher:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude has finished\" with title \"Claude Code\" sound name \"Glass\"'"
}
]
}
]
}
}
sound name takes any sound in /System/Library/Sounds, such as Glass, Ping or Hero. If you only want a sound, afplay /System/Library/Sounds/Glass.aiff works as the command. More on this event in the Stop hook guide.
04Show the real message
Hooks receive JSON on standard input. The Notification input has a message field, such as "Claude needs your permission". A small script can put that text in the notification. Save this as ~/.claude/hooks/notify-mac.sh:
#!/bin/bash
msg=$(jq -r '.message // "Claude Code needs your attention"')
osascript -e 'on run argv' \
-e 'display notification (item 1 of argv) with title "Claude Code"' \
-e 'end run' "$msg"
Passing the text as an argument, not inside the AppleScript string, means quotes in the message cannot break the command. Make it executable with chmod +x ~/.claude/hooks/notify-mac.sh and set the hook command to ~/.claude/hooks/notify-mac.sh. The script needs jq (brew install jq if jq --version fails).
05terminal-notifier: click to return to your terminal
A notification from osascript opens Script Editor when clicked, which is not where you want to go. terminal-notifier lets you choose the app that comes forward:
brew install terminal-notifier
terminal-notifier -title "Claude Code" -message "Claude has finished" -sound default -activate com.googlecode.iterm2
Use com.apple.Terminal for Apple Terminal, or the bundle ID of your editor. The same command goes in the hook's command field. Allow notifications for terminal-notifier in System Settings the first time.
06Focus and Do Not Disturb
All of these are ordinary macOS notifications, so a Focus mode can hide them. If they vanish at certain times, check System Settings > Focus and allow the app that posts them (Script Editor, terminal-notifier or your terminal). For notifications that respect your own hours instead, see quiet hours and Do Not Disturb.
07Where Claude Code Notifier fits
The hooks above are a good answer if you sit at your Mac and only want a nudge. They stop being enough when you step away, because a Mac notification cannot reach you in the kitchen and cannot answer Claude.
Claude Code Notifier is an independent tool (made by TouchTap, not by Anthropic) built on the same hooks. Its free command line tool, ccn, runs on Mac, Windows and Linux. Install it on your Mac with:
curl -fsSL https://claudecodenotifier.com/install.sh | sh
or with Homebrew: brew trust --formula touch-tap/tap/ccn && brew install touch-tap/tap/ccn. It adds its hook entries to ~/.claude/settings.json, leaves the rest of the file alone and keeps a backup. From then on it shows a macOS notification when Claude finishes, needs a permission or asks a question, with no network and no phone needed.
If you pair an iPhone or Android phone, the same events arrive as push notifications, and you can allow or deny a permission, pick an answer to Claude's question, or send the next instruction from the phone. If you typed in the last 30 seconds, the terminal asks as usual and nothing waits on the phone. See pairing a phone and the ccn page. The phone apps need a subscription or lifetime purchase; see pricing.
If you add ccn, remove your own osascript hooks or you will see two notifications for each event. There is also an older Mac menu bar app; do not run it alongside ccn on the same Mac.
08FAQ
Why does my osascript hook show nothing?
Most often Script Editor has no notification permission, so macOS drops the notification without an error. Run osascript -e 'display notification "test"' once, then allow Script Editor in System Settings > Notifications. If it still fails, check that a Focus mode is not active and that /hooks lists your hook.
Does Claude Code send macOS notifications without any setup?
Yes, in Ghostty and Kitty, and in iTerm2 once its Notification Center alerts are on. Apple Terminal and IDE terminals get no desktop notification by default, so use a hook or ccn there.
How do I get a notification as soon as Claude finishes, not a minute later?
Use a Stop hook. The idle_prompt notification only fires about 60 seconds after Claude finishes and only if you have not typed, while Stop runs at the end of every turn.
Can I hear a sound instead of seeing a banner?
Yes. Use afplay /System/Library/Sounds/Glass.aiff as the hook command, or add sound name "Glass" to the osascript command to get both.
Can I approve a permission from the macOS notification?
Not with osascript or terminal-notifier: they only display text. Answering a permission from a hook needs the PermissionRequest event; see answering permission requests from a hook or approve from your phone with Claude Code Notifier.