System notifications when Claude finishes.
macOS and Linux system notifications for Claude Code — duration-filtered so short tasks don't spam you. When Claude finishes a long job in the background, a notification fires carrying the repo name, branch, and Claude's actual last message.
claude plugin install notify@apurvbazari-plugins
/notify:setup
From Claude stopping to a system alert
A linear pipeline runs on every hook event. Click a stage to see detail in the side panel.
The duration filter is the key primitive — each event carries its own minDurationSeconds threshold. Below it, the script exits silently. Above it, it builds a repo / branch subtitle from git and extracts Claude's actual last message as notification content. The script always exits 0, so a missing backend or failed notification can never block Claude.
Intentionally minimal
notify does one thing well: native desktop alerts, duration-filtered, with project context. Install globally to get alerts across every Claude Code session, or per-project to tune settings per repo. Both scopes coexist — project-local inherits and overrides.
Three events, each independently configurable
Notification content is extracted from Claude's real last message. Each event has its own enabled flag, sound, and duration threshold. Edit notify-config.json directly — changes take effect immediately, no re-running setup.
| Event | When | Default sound | minDurationSeconds | Default state |
|---|---|---|---|---|
| stop | Claude finishes a response | Hero | 30 | enabled |
| notification | Claude needs your attention | Glass | 0 | enabled |
| subagentStop | A subagent finishes work | — | — | disabled (too noisy) |
stop fires on every Claude response — the 30-second default keeps it quiet for fast turns (typo fix, quick answer) while still firing on substantive work. notification always fires (threshold 0) because it means Claude is actively waiting on you. subagentStop is opt-in only.
Three user-facing skills
All under /notify:<name>. check is auto-invocable and read-only; setup and uninstall are destructive and require explicit user invocation.
| Skill | Invocation | What it does |
|---|---|---|
| /notify:setup | user-only | Detects platform, installs the backend (terminal-notifier via Homebrew on macOS, notify-send from libnotify on Linux), detects your editor for click-to-focus, asks global vs per-project, configures all three events, and sends a test notification. |
| /notify:check | auto | Health check — reports which scopes are installed, shows the precedence-merged config the hook will actually use, and fires a test notification to confirm end-to-end wiring. |
| /notify:uninstall | user-only | Surgically removes notify-owned hooks from settings.json, deletes notify-config.json, and offers to uninstall the backend if nothing else depends on it. Leaves unrelated hooks untouched. |
| wizard | The interactive Q&A that customizes sounds, durations, matcher regex, and per-event toggles. A building block invoked by setup (user-invocable: false) — not a command you run. |
Per-event knobs in notify-config.json
All configuration lives in notify-config.json, written by /notify:setup. A project-local file inherits all global keys and overrides only what it sets — both scopes coexist. Edit directly after setup; the hook reads it on every invocation.
Turn any event on or off without re-running setup. true or false per event.
14 macOS system sounds (Hero, Glass, Ping, Purr, Pop, Submarine, …). Maps to urgency level on Linux.
Suppress this event if elapsed time is below this value. The substantive-work knob. Set 0 for always-fire.
Bundle ID of the app to bring forward on notification click (e.g. VS Code, Cursor, iTerm2). macOS only.
Shown when contextual content can't be extracted from Claude's response. Static safety net.
Install globally for all sessions, per-project to override. Project-local inherits all global keys and overrides what it sets.
Cross-platform: macOS and Linux
Same hook, two backends. Duration filter and repo / branch subtitle work everywhere. Sounds and click-to-focus are richer on macOS.
14 system sounds, click-to-focus via bundle ID, title + subtitle + message, Homebrew install. JSON parsed via jq with python3 fallback.
Urgency levels (low/normal/critical mapped from sound setting), app-name, message body. Duration filter and repo/branch subtitle work identically.
notify-send reaches the Linux desktop only if a notification daemon is running. For Windows-side toasts, route through a Windows notifier (e.g. wsl-notify-send) or run Claude Code natively on Windows.