Skip to content

pramirez140/claude-code-mac-notifications

claude-code-mac-notifications

License: MIT Latest release CI Platform Claude Code

Native macOS notifications for Claude Code — a 3-to-4 word AI-summarized banner when Claude finishes a turn, an instant "Needs your input" banner with a sound when Claude is waiting on you, and a click that jumps you back to the exact terminal window/tab the session is in.

You start a long Claude task and tab away. Two things can happen, and you want to know about both:

  1. Claude is done. A banner pops up titled Claude · <project> with a body like "Refactored Auth Module" or "Researched Game Server Hosting". The body is a Title-Case action phrase generated by Haiku from the last assistant message. Click it → you're back in the right Terminal/iTerm tab.
  2. Claude needs you. A permission prompt or AskUserQuestion is waiting. Same banner pattern, body reads "Needs your input", plays the system sound. Click it → you're back in the right tab to answer.

Demo GIF coming soon — for now: see the Verifying it works section. Looking for a screen recording? PRs welcome.


Table of contents


Features

Stop banner Fires on Claude Code's Stop event. Body is a 3-4 word AI summary produced by claude -p --model haiku against the last assistant message. Falls back to first-N-words on LLM failure.
Input-needed banner Fires on the Notification event (permission prompts, AskUserQuestion, idle timeouts). Body reads "Needs your input". Plays a system sound. No LLM call — instant fire (~40ms).
Click-to-focus the right window Captures the Claude session's TTY at hook-fire time, then matches it via AppleScript on click. Targets the exact window/tab for Apple Terminal and iTerm2. Other terminals fall back to whole-app activation.
Project-scoped title Claude · <basename of cwd> so multiple sessions are visually distinct.
Clean dedup A fixed notification group means newer alerts displace older ones. Notification Center stays tidy; background processes are bounded.
Claude-branded thumbnail Orange burst icon on the right of the banner. Bundled — no Claude.app dependency.
Async, non-blocking Hooks run in the background. Claude Code's UI is never blocked, even on the slowest LLM-summary turn.
Two install paths Use whichever fits your workflow — plain install.sh or Claude Code plugin marketplace. The shipped scripts auto-detect their context.

Requirements

Tool Why
macOS Uses osascript and AppleScript
Homebrew Installs terminal-notifier for you
jq JSON manipulation in scripts (brew install jq)
Claude Code CLI Provides the claude binary used for summaries

Notification permission for terminal-notifier is granted automatically the first time it fires. If your banners never appear, see Verifying it works.

Installation

Option 1 — install script (no plugin marketplace)

git clone https://github.com/pramirez140/claude-code-mac-notifications.git
cd claude-code-mac-notifications
./install.sh

The installer is idempotent — re-run it any time to refresh the scripts. It:

  1. Verifies macOS, jq, and the Claude CLI.
  2. Installs terminal-notifier via brew if missing.
  3. Copies hook scripts and the icon into ~/.claude/hooks/.
  4. Patches ~/.claude/settings.json to register the Stop and Notification hooks (replaces any prior entries pointing at our scripts; preserves everything else).
  5. Validates the resulting JSON before clobbering the original. If validation fails, your settings are left untouched.

Option 2 — Claude Code plugin

/plugin marketplace add pramirez140/claude-code-mac-notifications
/plugin install claude-code-mac-notifications@claude-code-mac-notifications

Restart Claude Code, or open /hooks once, so the watcher picks up the new hooks.

The plugin still needs terminal-notifier on your machine — brew install terminal-notifier if the hook complains.

Verifying it works

After install (or /hooks reload):

  • Stop: end any Claude Code turn — you should see a banner ~5-10 seconds later.
  • Notification: trigger an AskUserQuestion, or if you're not running with --dangerously-skip-permissions, do something that surfaces a permission prompt. Banner + sound should fire instantly.

If nothing appears:

tail -20 ~/.claude/hooks/notify-stop.log
tail -20 ~/.claude/hooks/notify-input.log

Common causes:

  • Do Not Disturb / Focus mode is on. Banners are suppressed silently. Toggle DND off in Control Center.
  • Notification permission for terminal-notifier isn't granted. Open System Settings → Notifications, find terminal-notifier, set Alert Style to Banners or Alerts.
  • Hook didn't fire — check the log. If there's no recent fired: line, the hook didn't run. Verify ~/.claude/settings.json has the Stop and Notification entries:
    jq '.hooks.Stop, .hooks.Notification' ~/.claude/settings.json
  • claude -p is timing out on a long message. Hook timeout is 60s by default — bump it in settings.json if your machine is slow.

Customization

Change the summary style

The summary is produced by a claude -p --model haiku call against this prompt (hooks/notify-stop.sh):

You will receive the last message from a coding assistant. Reply with a 3-to-4
word summary of what was just done. Action phrase only — no period, no quotes,
no preamble, no emoji. Title-Case the words.

Edit it to suit your taste — funnier, lowercase, with emojis, Spanish, whatever.

Change the input-banner text

hooks/notify-input.sh has a fixed -message "Needs your input". Swap it for anything you like — "Claude needs you", "awaiting human", or pull a more specific message out of the hook's stdin payload.

Change the icon

Replace assets/claude-icon.png (or ~/.claude/hooks/claude-icon.png for script installs) with any 256×256 PNG.

Change the sound

notify-input.sh uses -sound "default". Replace with any name from System Settings → Sound → Sound Effects (e.g. Funk, Glass, Hero, Ping, Pop).

Change the click target on unsupported terminals

hooks/focus-terminal.sh covers Apple Terminal and iTerm2 with TTY-matching AppleScript. For Ghostty / WezTerm / Warp / VS Code / Cursor / Hyper / Alacritty / Kitty / Tabby we just activate the whole app. PRs welcome — see CONTRIBUTING.md.

Disable temporarily

/hooks

…and toggle the entry off, or remove it from ~/.claude/settings.json.

Model, auth, and estimated usage

Model selection

The Stop hook calls Claude Haiku 4.5 via claude -p --model haiku --strict-mcp-config --no-session-persistence. We picked Haiku because it's the fastest + cheapest model in the Claude family, and 3-to-4 word summaries don't need a smarter model. --strict-mcp-config skips MCP server loading (saves ~3s of startup), and --no-session-persistence prevents the one-shot summary calls from cluttering ~/.claude/projects/.

The Notification hook makes zero LLM calls — its body is a fixed string, so it's free and fires in ~40ms.

Resource inheritance

The hook is a thin wrapper around the claude CLI. It does not ship credentials, set ANTHROPIC_API_KEY, or use the --bare flag. That means it inherits whatever auth your interactive claude session uses:

  • Claude Pro / Max / Team (OAuth via macOS Keychain) → calls draw from your subscription's quota. $0 dollar cost.
  • Console / API key (ANTHROPIC_API_KEY in env) → billed at standard Haiku rates.
  • No authclaude -p fails silently and the hook falls back to a first-N-words summary; the notification still fires.

No API key, OAuth token, or other credential is ever read, written, logged, or transmitted by this project. You can verify by grep-ing the repo:

grep -RIn -E 'sk-ant-|ANTHROPIC_API_KEY|anthropic\.com/v1' .

Estimated token usage

Measured from real haiku invocations on a typical workstation:

Field Per call
Fresh input tokens ~10
Cache write (1h) ~7,500
Cache read ~35,000 (after first call)
Output tokens ~300-1,500 (variable; the trimmer takes the first line)

At Haiku 4.5 list pricing (~$1 / $5 / $2 / $0.10 per MTok for input / output / 1h cache write / 1h cache read), each Stop event costs about $0.015–0.020 for API users. The Notification hook is free.

For a back-of-the-envelope:

Daily Claude turns API cost / day API cost / month
25 ~$0.40 ~$12
50 ~$0.85 ~$25
100 ~$1.70 ~$50

If you're on a subscription this is just quota usage and you can ignore the dollar figures. If you're on API-key billing and the cost matters, look at adding --tools '' to the claude -p invocation in hooks/notify-stop.sh — it drops the ~30K cached tool-definition tokens that the summary task doesn't need (we keep tools enabled by default to avoid edge-case CLI weirdness).

How it works

┌─────────────────┐  Stop event   ┌──────────────────────┐
│  Claude Code    │ ────────────▶ │  notify-stop.sh      │
│  (your turn     │  (JSON on     │                      │
│   just ended)   │   stdin)      │  • read transcript   │
└─────────────────┘               │  • claude -p haiku   │
                                  │  • resolve PARENT_TTY│
                                  │  • detach notifier   │
                                  └──────────────────────┘
                                              │
┌─────────────────┐  Notification              │
│  Claude Code    │ ────────────▶  ┌─────────────────────┐
│  (waiting on    │  event         │  notify-input.sh    │
│   user input)   │                │                     │
└─────────────────┘                │  • fixed message    │
                                   │  • sound: default   │
                                   │  • detach notifier  │
                                   └─────────────────────┘
                                              │
                                              ▼
                              ┌────────────────────────────┐
                              │   macOS notification UI    │
                              │   (Notification Center)    │
                              └────────────────────────────┘
                                              │ click
                                              ▼
                              ┌────────────────────────────┐
                              │   focus-terminal.sh        │
                              │   AppleScript: match TTY   │
                              │   → focus exact window/tab │
                              └────────────────────────────┘

Both hooks are async: true so they never block Claude's UI. terminal-notifier itself idles up to ~30 seconds waiting for the click. The fixed -group "claude-stop" means a new notification displaces the previous, so process count and Notification Center clutter stay bounded.

Engineering notes — why so much code for a notification

The non-obvious things that bit us while building this. They might bite you too if you fork:

  1. -sender com.anthropic.claudefordesktop looks reasonable but silently fails. macOS routes notifications under the sender's bundle, and if Claude.app doesn't have notification permission, every notification disappears into the void. We post under terminal-notifier's own bundle and use -contentImage for the Claude logo instead.
  2. -appIcon is a no-op on macOS Tahoe. Only -contentImage (the right-side thumbnail) is honored. The left icon is whatever the sender bundle ships with — there's no override short of repacking terminal-notifier.app.
  3. The transcript JSONL has multiple "assistant" entries per turn (text → tool_use → tool_result → text). Naïvely picking the last one with tail -1 gives you a tool_use block with no text. We filter for entries containing a text content block.
  4. Embedded \n characters inside the assistant text break tail -1 when applied after jq. We normalize whitespace inside jq with gsub("[\\n\\r\\t]+"; " ") so the whole message is one logical line before tail.
  5. timeout doesn't exist on stock macOS. Use perl -e 'alarm N; exec @ARGV' for portability.
  6. claude -p --model haiku startup is dominated by MCP-server loading. --strict-mcp-config cuts ~3s. --no-session-persistence stops one-shot summary calls from cluttering ~/.claude/projects/.
  7. terminal-notifier blocks ~37 seconds after dispatch waiting for click events with -execute. Detach it with nohup ... & disown so the hook script returns immediately.
  8. TTY at hook-fire time isn't the hook's own TTY — hooks run with stdin redirected, so tty returns "not a tty". Walk the process ancestry via ps -p $P -o tty= until you find a real device.

Roadmap

Things on the table for a future minor version:

  • Suppress notifications when the originating terminal is already frontmost (no point notifying yourself if you're already looking at it).
  • Configurable sound + Notification Style per-event via env vars or a small config file.
  • First-class window-targeting for Ghostty (it's gaining AppleScript hooks) and WezTerm (it has a CLI for tab activation).
  • Optional GitHub-Action-built shellcheck badge.
  • Real demo GIF.

If you'd like any of these — or have other ideas — open a feature request.

Uninstall

./uninstall.sh

Surgically removes our hook entries from ~/.claude/settings.json, deletes the scripts and icon. Leaves terminal-notifier installed (other tools may use it; remove it explicitly with brew uninstall terminal-notifier if you want).

Contributing & support

License

MIT © 2026 Pablo Ramirez

Acknowledgements

About

Native macOS notifications for Claude Code stop events — 3-4 word AI summaries, click-to-focus your specific terminal window, Claude-branded icon. Ships as a plugin and an install script.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages