auriga-notify plugin
Fires a native macOS notification (banner + sound) whenever Claude Code
emits a Notification event — permission prompts, idle input waits — so
you get pulled back to the terminal without having to watch it.
Customize
Project-scope customization lives at ./.claude/auriga-notify/config.json.
User-scope customization lives at ~/.config/auriga-cli/notify/config.json.
You can also point AURIGA_NOTIFY_CONFIG at a JSON file for ad hoc testing.
{
"icon": "icon.png",
"sound": "Submarine"
}
sound— any macOS built-in sound name (case-sensitive):Basso,Blow,Bottle,Frog,Funk,Glass,Hero,Morse,Ping,Pop,Purr,Sosumi,Submarine,Tink. You can also drop a.aiffor.cafinto~/Library/Sounds/and reference it by filename without the extension.icon— relative to the config file's directory or an absolute path. Replace the migratedicon.pngwith any 512×512 PNG to brand it yourself. If no custom icon exists, the plugin falls back to its bundledassets/icon.png.sender(optional, advanced) — bundle ID of a macOS app whose notification permission the notification should piggy-back on (e.g."com.apple.Terminal","com.googlecode.iterm2"). Default is unset, which uses the notification backend's own bundle — the most reliable path because brew authorizes it at install time. Setting this to a bundle whose notification permission, banner style, or Focus settings are misconfigured will silently swallow notifications, so leave it alone unless you have a specific reason.activate(optional) — bundle ID of the app to bring to the foreground when you click the banner. By default the hook auto-detects the terminal Claude Code is running in via the$__CFBundleIdentifierenv var that macOS Launch Services injects into every descendant of an app it launched, so clicking the banner takes you straight back to the terminal that asked for attention — Apple Terminal, iTerm2, Ghostty, Warp, VS Code's integrated terminal, whatever — with no mapping table to maintain. Set this to a string to force a specific app (e.g."com.microsoft.VSCode"to always jump to VS Code), or tofalseto opt out entirely (banner is purely informational, click does nothing).soundOnlyWhenFocused(defaulttrue) — when the terminal that launched Claude is the frontmost app at notification time, drop the banner and play only the sound. Rationale: you're already looking at the conversation, the banner is visual noise but a chime still pulls your ear back. Setfalseto always show the full banner regardless of focus. Detection usesosascriptagainstSystem Events, which may trigger a one-time macOS Automation permission prompt on first run — denying it is safe (the hook treats permission failure as "can't tell" and shows the full banner). If your customsoundname doesn't resolve to any file on disk, the focused path also falls through to the full banner so you always get a signal.
The hook is macOS-only at runtime. On other platforms it exits silently
without doing anything, so it's safe to commit into a repo shared with a
cross-platform team if you registered it in .claude/settings.json.
Test it
After editing your config or replacing icon.png, fire a fake
Notification event end-to-end without waiting for Claude:
node plugins/auriga-notify/scripts/test-notify.mjs
This invokes notify.mjs exactly the way Claude Code would, so what you
see + hear is what you'll get in real use.
No sound but the banner shows up? macOS's alert volume is independent from the main volume slider. Open System Settings → Sound → Alert volume and make sure it isn't at zero — that's the most common cause. Other suspects: Focus / Do Not Disturb mode, or the notification permission for your terminal app being set to "None" in System Settings → Notifications.
How it works
scripts/notify.mjs reads the Notification event payload from stdin, then
decides between three paths:
- Sound only — when
soundOnlyWhenFocusedis on AND the launching terminal's bundle ID matches the frontmost app's bundle ID. Plays the configured sound viaafplay(looking under~/Library/Sounds/then/System/Library/Sounds/). Returns immediately. - Full banner + sound — every other case (terminal not focused,
focus check disabled in config, focus undetectable, sound miss in
the focused path,
AURIGA_NOTIFY_FORCE_BANNER=1). Picks the first available notification backend:
-
alerter(preferred) — Swift-based notification CLI with--app-iconfor the small top-left icon next to the title. Install it separately withbrew install vjeantet/tap/alerter; without it, the hook falls back toosascript. alerter blocks until the user clicks (or the notification is replaced), so the hook spawns it through a detached background worker and exits immediately — Claude Code is never blocked. The worker watches alerter's stdout for@CONTENTCLICKEDand, on click, runsosascriptto bring the resolvedactivatebundle to the foreground.No
--timeoutis set, so the click handler stays alive for as long as the notification lives in Notification Center — clicking later (after the banner has slid off screen) still works. To avoid accumulating worker processes when many notifications fire without being clicked, every notification uses a per-project group ID (auriga-notify-<sha8(cwd)>): a new notification in the same project replaces the previous one and the old alerter exits via@CLOSED, while notifications from other projects use a different group and don't cannibalize each other. Trade-off: within a single project the most recent notification is the only clickable one — older ones in NC become inert. -
osascript(fallback) —display notificationvia AppleScript. Always present on macOS. No custom icon, no click activation. Used when alerter isn't installed (e.g. brew tap install failed at install time, or the hook is in a project that hasn't been re-installed by the auriga-cli installer).
Re-installing
auriga-notify is opt-in. Install it explicitly:
npx -y auriga-cli install plugins --plugin auriga-notify
If an older standalone .claude/hooks/notify/ install exists, the CLI
migrates config.json and icon.png into the plugin-owned config directory
without overwriting existing files, removes the old settings marker, and
deletes the legacy hook directory.