Drop this into ~/.claude/settings.json (merge with existing keys — don't replace the whole file).
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Task finished\" with title \"Claude Code\" sound name \"Glass\"'"
}
]
}
],
"Notification": [
{
"matcher": "permission_prompt|idle_prompt",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Needs your input\" with title \"Claude Code\" sound name \"Funk\"'"
}
]
}
]
}
}Available sounds: Glass, Funk, Ping, Pop, Submarine, Tink, Hero, Blow. Drop the sound name clause for silent notifications.
brew install terminal-notifierThen:
"command": "terminal-notifier -title 'Claude Code' -message 'Task finished' -sound Glass -group claude-code"-group claude-code makes new notifications replace older ones instead of stacking.
On first fire, macOS asks whether the notifier app (Script Editor for osascript, or terminal-notifier) may post notifications. Approve once in System Settings → Notifications.
If nothing appears:
- Check Do Not Disturb / Focus isn't suppressing them.
- Confirm the app has notification permission in System Settings.
- Run the raw
osascriptcommand in Terminal to verify it works standalone.
jq -e '.hooks.Stop[].hooks[].command' ~/.claude/settings.jsonIf you edit settings.json while Claude Code is running, open /hooks once or restart the session — the watcher only picks up files that existed at session start.