Reliable macOS desktop notifications for Claude Code running in Apple Terminal — that actually focus the right tab when you click them.
- 🔐 Allow? — when Claude needs permission, the banner shows the specific pending
action (
Bash — run the tests,Write — config.swift, a URL, a search query…). - 💬 Waiting for you — when Claude has been idle waiting on your input.
- ✅ Done — when Claude finishes a turn, with the topic of what it did. Suppressed when you're already looking at that tab, so it's not per-turn spam.
- Click any notification → it jumps to the exact Terminal tab running that session.
Each banner carries the Terminal icon, a state (🔐 / 💬 / ✅), the project as a subtitle, and the specific action or topic. Different sessions stay distinct — click one to land on exactly that tab.
terminal-notifier 2.0.0 posts a banner and then exits immediately. Its click
actions (-execute / -activate) are delivered to a delegate that only runs while the
process is alive — so on modern macOS (Sonoma / Sequoia) clicking a terminal-notifier
notification does nothing. Even system-level -sender app activation fails.
This uses alerter instead, which stays alive
and blocks until you click, then reports the activation. That's the piece that makes
click-to-focus work. On click, an AppleScript matches the Terminal tab by its tty and
raises the window — so you land on the exact session, not just "Terminal somewhere".
- macOS, Apple Terminal (
Terminal.app) jq(brew install jq)- Xcode command line tools (for
codesignverification)
git clone https://github.com/<you>/claude-code-terminal-notify.git
cd claude-code-terminal-notify
./install.shThe installer downloads alerter from its GitHub release, verifies its code-signing
Team ID before trusting it, copies it and notify.sh into ~/.claude/hooks/, and
merges the Notification + Stop hooks into ~/.claude/settings.json (backed up first).
On the first notification you click, macOS will ask to allow notifications and to let it control Terminal — approve both (one-time).
~/.claude/settings.json runs notify.sh on two Claude Code hook events:
{
"hooks": {
"Notification": [{ "matcher": "", "hooks": [{ "type": "command", "command": "/Users/you/.claude/hooks/notify.sh notify" }] }],
"Stop": [{ "matcher": "", "hooks": [{ "type": "command", "command": "/Users/you/.claude/hooks/notify.sh stop" }] }]
}
}- The hook returns in a few ms;
alerterruns detached and blocks in the background. - Notifications are grouped per-tab (
--group claude-<tty>) so a new one replaces the old (and the oldalerterprocess exits — no pile-up). A 1h--timeoutis an orphan backstop. - Banner text is enriched from the session transcript (
transcript_path): the pending tool'sdescription/file_path/url/query, or the last assistant message topic.
- Emojis / wording / sounds: edit the
case "$mode"block innotify.sh. - Don't want "Done" notifications: remove the
Stophook fromsettings.json. - Always notify on Done (even when focused): delete the
is_focusedearly-return in thestopbranch.
The tab-focus logic is Apple-Terminal-specific — it matches Terminal.app tabs by tty
via AppleScript. The notification + transcript enrichment work anywhere, but you'd need to
rewrite the focus branch (and is_focused) for your terminal's scripting model. PRs welcome.
rm ~/.claude/hooks/notify.sh ~/.claude/hooks/alerter
# then remove the "Notification" and "Stop" entries from ~/.claude/settings.jsonBuilt on alerter by Valère Jeantet.
