Set up macOS notifications for Claude Code in ten minutes: distinct sounds for permission requests vs task completion, banners that name the project, and click-to-focus. Works in Ghostty, iTerm2, Terminal.app, Kitty, Warp, and Alacritty.
Share this post:
Export:

TL;DR — You do not need to read this article. Hit the markdown button at the top of this page, copy everything, paste it into Claude Code, and say "set this up for me." Claude will read the post, create the hooks, and you will have sound and visual notifications working in two minutes. The article is here so you understand what it does. But the fastest path is: copy, paste, done.
Updated July 2026 — added the built-in terminal-bell option, a Stop-hook variant, phone notifications, and an FAQ.
The zero-setup option first: Claude Code ships a built-in notification channel. One command gets you a terminal bell on every attention event:
claude config set --global preferredNotifChannel terminal_bell
That's the whole feature — a bell, no banners, no way to tell five instances apart. Fine for one terminal. The rest of this post builds the real thing: distinct sounds per event type, macOS banners that name the project, and click-to-focus.
I run Claude Code across multiple terminal instances simultaneously. At any given moment I might have five or six instances working — one in my CFO repo, one in polaris, one doing research, another processing data. The problem is obvious: I'm context-switching between browser tabs, Slack, documents, and when Claude finishes a task or needs my approval, I have no idea unless I'm staring at that specific terminal.
Today I solved this properly, and it took about ten minutes. Here's exactly how to set it up so you can do the same. This works in any macOS terminal — Ghostty, iTerm2, Terminal.app, Kitty, Warp, Alacritty, whatever you use.
Claude Code runs in your terminal. Terminals don't have push notifications. When Claude finishes a complex task and prints "ready for your input," you might not see it for five minutes because you're reading a doc in another window. When Claude needs permission to run a destructive command, it's blocked waiting on you while you're answering email.
Multiply this by several concurrent instances and you're leaving serious velocity on the table.
Claude Code has a hooks system that fires shell commands on specific events. The Notification hook fires when Claude needs your attention. We're going to wire it up with:
macOS ships with 14 system sounds. Here's a quick script to audition them all:
#!/bin/bash
sounds=(Basso Blow Bottle Frog Funk Glass Hero Morse Ping Pop Purr Sosumi Submarine Tink)
echo "=== macOS System Sound Demo ==="
echo "Playing ${#sounds[@]} sounds with 2-second gaps"
echo ""
for i in "${!sounds[@]}"; do
num=$((i + 1))
echo " [$num/${#sounds[@]}] ${sounds[$i]}"
afplay "/System/Library/Sounds/${sounds[$i]}.aiff"
sleep 2
done
Save that as play-sounds.sh, run it, and pick three sounds for three scenarios:
| Event | What It Means | My Pick |
|---|---|---|
| Permission | Claude needs you to approve a tool call | Funk |
| Done | Task complete, ball's in your court | Hero |
| Compaction | Context window compressing, no action needed | Sosumi |
The built-in osascript notifications work fine for sound + banner, but they don't support click-to-focus. terminal-notifier does — when you click the notification, it activates your terminal app.
brew install terminal-notifier
The -activate flag needs your terminal's bundle identifier. Here are the common ones:
| Terminal | Bundle ID |
|---|---|
| Ghostty | com.mitchellh.ghostty |
| iTerm2 | com.googlecode.iterm2 |
| Terminal.app | com.apple.Terminal |
| Kitty | net.kovidgoyal.kitty |
| Warp | dev.warp.Warp-Stable |
| Alacritty | org.alacritty |
| WezTerm | com.github.wez.wezterm |
If your terminal isn't listed, you can find its bundle ID with:
osascript -e 'id of app "YourTerminalName"'
Edit your global Claude Code settings at ~/.claude/settings.json. Replace com.mitchellh.ghostty with your terminal's bundle ID from the table above:
{
"hooks": {
"Notification": [
{
"matcher": "permission",
"hooks": [{
"type": "command",
"command": "terminal-notifier -title 'Claude Code' -subtitle \"Permission Request [$(basename $PWD)]\" -message 'Approval needed — check your terminal' -sound Funk -activate com.mitchellh.ghostty"
}]
},
{
"matcher": "compact",
"hooks": [{
"type": "command",
"command": "terminal-notifier -title 'Claude Code' -subtitle \"Compaction [$(basename $PWD)]\" -message 'Compacting context window — no action needed' -sound Sosumi -activate com.mitchellh.ghostty"
}]
},
{
"matcher": "",
"hooks": [{
"type": "command",
"command": "terminal-notifier -title 'Claude Code' -subtitle \"Done [$(basename $PWD)]\" -message 'Task complete — ready for your input' -sound Hero -activate com.mitchellh.ghostty"
}]
}
]
}
}
Let's break down what's happening:
The matcher field filters which notifications trigger each hook. "permission" catches permission prompts, "compact" catches compaction events, and "" (empty string) is the catch-all for everything else — which in practice means "task complete."
The $(basename $PWD) trick injects the current working directory name into the notification subtitle. When you're running five instances, seeing "Done [cfo]" vs "Permission Request [polaris]" tells you exactly which instance needs you without switching windows.
The -activate flag is the magic — clicking the notification brings your terminal to the foreground. This is the only part that's terminal-specific, and it's just a one-word swap of the bundle ID.
By default, macOS notification banners disappear after a few seconds. If you want them to persist until you dismiss them:
System Settings → Notifications → terminal-notifier → change "Banners" to "Alerts"
Alerts stay on screen until you click Close or Action. This is the right choice when you're deep in another task and don't want to miss the notification.
Run this from any terminal to verify (swap in your bundle ID):
terminal-notifier -title 'Claude Code' \
-subtitle "Done [$(basename $PWD)]" \
-message 'Task complete — ready for your input' \
-sound Hero \
-activate com.mitchellh.ghostty
You should see a notification banner with your directory name, hear the Hero sound, and clicking it should bring your terminal to front.
After this setup, my workflow looks like this:
The subtle but important detail: three distinct sounds means you can triage by ear. Sosumi (compaction) means "ignore, Claude is handling it." Hero (done) means "whenever you're ready." Funk (permission) means "Claude is blocked on you — go now."
I run Claude Code as my primary development interface. On any given day I might have instances working across a CFO financial repo, the main product codebase, a research project, and a one-off script. The limiting factor is no longer Claude's speed — it's my attention allocation across instances.
Before notifications, I was manually cycling through terminals to check status. That's maybe 30 seconds each time, multiplied by dozens of check-ins per day, across multiple instances. Call it 15-20 minutes of pure waste daily, plus the cognitive cost of interrupted flow.
After notifications, Claude tells me exactly when and where it needs me. I stay in flow on whatever I'm doing until I hear the sound. Simple tools, compounding returns.
A few things you could extend from here:
The hooks system is simple — it's just "run this shell command when this event fires." That simplicity is the feature. Stay light on tooling, heavy on context.
| Component | What | Where |
|---|---|---|
| Hook config | Global Claude Code settings | ~/.claude/settings.json |
| System sounds | macOS built-in audio files | /System/Library/Sounds/ |
| terminal-notifier | Click-to-focus notifications | brew install terminal-notifier |
| Alert persistence | Make banners stay on screen | System Settings → Notifications → terminal-notifier → Alerts |
| Find bundle ID | For any terminal app | osascript -e 'id of app "AppName"' |
Ten minutes of setup, permanent quality-of-life improvement. Ship it.
Two ways. The quick one: claude config set --global preferredNotifChannel terminal_bell rings your terminal bell on attention events. The good one: add a Notification hook to ~/.claude/settings.json that runs any shell command — that's the three-tier setup in this post, and it takes about ten minutes.
The empty-string matcher ("") on the Notification hook is the catch-all, and in practice that means task completion. If you want a dedicated end-of-turn signal, the Stop hook fires every time Claude finishes responding — wire the same terminal-notifier command to it and pick a different sound.
Any of them. The hook runs a shell command, so the terminal is irrelevant except for one flag: -activate needs your terminal's bundle ID for click-to-focus. The table above covers Ghostty, iTerm2, Terminal.app, Kitty, Warp, Alacritty, and WezTerm; osascript -e 'id of app "YourTerminal"' finds any other.
Yes — the hook command can be anything, including a curl. Point it at ntfy.sh (free, no signup: curl -d "Claude needs you" ntfy.sh/your-private-topic) or Pushover, and a long-running task can page you when you've walked away from the desk entirely.
The hooks system is identical; only the notifier changes. On Linux swap terminal-notifier for notify-send and the sound for paplay. On Windows, PowerShell's BurntToast module covers the banner. The $(basename $PWD) project-name trick works everywhere.
Three usual suspects: macOS treats terminal-notifier as its own app, so check System Settings → Notifications → terminal-notifier is allowed (and set to Alerts if you want them to persist); Do Not Disturb / Focus modes silently eat banners; and if you only hear sound with no banner, the hook is firing fine — it's purely a notification-permission issue.
Zero to Hero: Building with Claude Code
A complete beginner's guide to setting up your development environment and building your first project with Claude Code. Covers Mac and Windows, ...
Claude Code as My GitHub Project Manager: 35 Issues Triaged in Minutes
How Claude Code helped me triage 35 GitHub issues, close 9 completed features, create app labels, and build a ghetto newsletter system - all while shi...
Two Claude Codes, Two Repos, One Solution: A Multi-Agent Workflow Story
How two Claude Code instances collaborated across different repositories to solve a 500 error by mining proven patterns from production code. A meta-n...
Get notified when I publish new blog posts about game development, AI, entrepreneurship, and technology. No spam, unsubscribe anytime.
Loading comments...
Published: March 12, 2026 6:08 PM
Last updated: July 12, 2026 10:41 PM
Post ID: 58918292-7c3b-4796-bd81-74d5e7814ab8