Desktop notifications, only when it matters.
notify fires a macOS or Linux system notification when Claude Code finishes a task — carrying a repo / branch subtitle and Claude's actual last message. A per-event duration filter suppresses fast responses, so it only interrupts you when Claude has genuinely been working.
claude plugin install notify@apurvbazari-plugins
Then run /notify:setup to install the platform backend and wire the hooks.
A Stop-hook wrapper around your system notifier
/notify:setup detects your platform, installs the backend if needed (terminal-notifier via Homebrew on macOS, or notify-send from libnotify on Linux), detects your editor for click-to-focus, asks whether to install globally or per-project, and wires the hooks.
When Claude stops, the hook runs notify.sh, which reads your per-event config, extracts Claude's actual last message (not generic static text), builds a contextual repo / branch subtitle from git, and posts the notification. The script always exits 0 — a missing backend or a failed notification never blocks Claude.
Intentionally minimal
notify does one thing well: native desktop alerts, duration-filtered, with project context. For richer feature sets, the root README compares it honestly against community alternatives.
The duration filter
Each event carries a minDurationSeconds threshold. If the elapsed time since last activity is below it, the notification is silently skipped — so a 4-second typo fix never interrupts you, but a 12-minute refactor does.
The wrapper tracks last activity in a temp file ($TMPDIR/claude-notify-session-start). The filter applies to stop and subagentStop — set 30 on stop for substantive-work-only alerts. Leave 0 on notification so attention prompts always fire.
From Claude stopping to a system alert
A fixed pipeline runs on every hook event. Click a stage for detail.
The backend is chosen by platform: terminal-notifier on macOS (title, subtitle, message, sound, click-to-activate), or notify-send on Linux (app-name, urgency, message). JSON from stdin is parsed with a jq-first / python3-fallback helper, so neither tool is a hard dependency.
Skills & the setup wizard
Three user-facing skills (all /notify:<name>). check is auto-invocable; the two destructive skills require explicit invocation. One internal wizard is hidden from the menu.
| Skill | Invocation | What it does |
|---|---|---|
| /notify:setup | user-only | Detects platform, installs the backend, detects your editor + bundle ID for click-to-focus, asks global vs per-project, wires the hooks, and sends a test notification. |
| /notify:check | auto | Health check — reports which scopes are installed, the event configuration, the precedence-merged config the hook will actually use, and sends a test notification to confirm the wiring end-to-end. |
| /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. |
| /notify:wizard | The preference Q&A — sounds, durations, per-event toggles, matcher regex. Invoked by setup; hidden from the / menu. |
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.
| Event | When | Default |
|---|---|---|
| stop | Claude finishes a response | Enabled · Hero sound · minDurationSeconds: 30 |
| notification | Claude needs your attention | Enabled · Glass sound · minDurationSeconds: 0 |
| subagentStop | A subagent finishes work | Disabled (too noisy by default) |
Turn any event on or off without re-running setup.
Hero, Glass, Ping, Purr, Pop, Submarine and more on macOS; urgency level on Linux.
Suppress this event below the elapsed-time threshold. The substantive-work knob.
App to bring forward on click (VS Code, Cursor, iTerm2, …) via bundle ID — macOS only.
Shown when contextual content can't be extracted from Claude's response.
Project-local inherits all global keys and overrides only what it sets. Both scopes coexist.
Cross-platform: macOS and Linux
Same hook, two backends. The duration filter and the repo / branch subtitle work everywhere; sounds and click-to-focus are richer on macOS.
| Capability | macOS | Linux |
|---|---|---|
| Backend | terminal-notifier | notify-send (libnotify) |
| Sound | 14 system sounds | Urgency levels only |
| Click-to-focus | ✓ via bundle ID | — not supported |
| Duration filter | ✓ | ✓ |
| repo / branch subtitle | ✓ | ✓ |
| JSON parsing | jq → python3 fallback | jq → python3 fallback |
One run, then the filter at work
/notify:setup on macOS, then a Stop hook firing in two scenarios — one suppressed by the duration filter, one delivered.
> /notify:setup
Detecting platform … macOS
Checking for terminal-notifier … installing via Homebrew
Editor detected: VS Code (com.microsoft.VSCode)
Where should notifications be installed?
(a) Global — fire in every Claude Code session
(b) This project only
> a
Configuring three events:
stop enabled sound: Hero minDurationSeconds: 30
notification enabled sound: Glass minDurationSeconds: 0
subagentStop disabled (too noisy by default)
Writing notify-config.json to ~/.claude/
Adding hooks to ~/.claude/settings.json (Stop, Notification)
Sending test notification … ✓
# ── short task: "fix typo in README" ────────
[Stop hook fires]
[notify.sh: elapsed 4s < 30s threshold → silently skip]
(no notification — duration filter suppressed)
# ── long task: 12-minute refactor ───────────
[Stop hook fires]
[notify.sh: elapsed 743s ≥ 30s → notify]
┌──────────────────────────────────────┐
│ Claude Code │
│ feedback-saas / feat/onboarding-flow │
│ Refactored auth middleware … │
└──────────────────────────────────────┘
Sound: Hero · Click brings VS Code to front