English / 日本語
A macOS notifier for Claude Code. Fires native macOS notifications with a custom icon and click-through to your IDE, so the permission prompts and idle reminders Claude Code raises do not look like they came from Script Editor.
By default, hooks invoked through osascript -e 'display notification ...'
surface under the Script Editor identity. The icon is wrong, the click target
is wrong, and there is no way to override either with the standard tooling
because macOS has restricted the -sender override since Monterey.
This tool ships a small signed .app bundle whose sole job is to emit
UNUserNotificationCenter notifications with:
- a custom icon (Heroicons bell, Claude brand colors)
- a click-through that activates your IDE bundle (defaults to Visual Studio Code)
- configurable sounds per event label (
permission,idle,default) - safe input handling (length caps, allowlisted sound names and bundle IDs)
Several alternatives exist. Respect where it is due — these are worth checking if this one does not fit your setup.
- dazuiba/CCNotify — Python-based, VS Code jump on click.
- wyattjoh/claude-code-notification — Rust-based, simple notifications.
- polyphilz/ccnotifs — Shell + custom
.app, tmux pane teleport on click. - splazapp/claude-code-notification — Bash + Swift, multi-IDE click-through.
- Naveenxyz/claude-code-notifier — Shell, auto-detects 10+ IDEs and terminals.
- Attack surface explicitly closed off: allowlists for sound names and bundle
IDs, length caps for notification bodies, control-character stripping. Values
coming from
config.jsonand CLI arguments are never trusted blindly - Single Swift binary, no Python / Node / Rust runtime dependency
- License-clean icon derived from Heroicons (MIT), no SF Symbols constraint
- Sound labels in
config.jsondecouple hook wiring from sound choice - Universal binary (arm64 + x86_64), macOS 12.0 or later
- Bilingual README (English + Japanese)
- Complete OSS boilerplate: LICENSE, CONTRIBUTING, SECURITY, CODE_OF_CONDUCT, TRADEMARKS
- macOS 12.0 Monterey or later (arm64 / x86_64 universal)
- Xcode Command Line Tools (
xcode-select --install) - Homebrew formulae for the build step:
librsvg— rasterizes the Heroicons SVG at build time
brew install saiso/tap/claude-code-notifierThis builds from source under Homebrew's prefix. The .app is placed at
$(brew --prefix)/opt/claude-code-notifier/libexec/Claude Code Notifier.app
and a claude-code-notifier wrapper is linked into $(brew --prefix)/bin/
so you can also invoke it as claude-code-notifier "message" "title" "sound".
Dependencies (librsvg and Xcode Command Line Tools) are resolved by
Homebrew where possible. On the first notification, macOS will ask for
notification permission.
git clone https://github.com/saiso/claude-code-notifier.git
cd claude-code-notifier
./install.shThe installer verifies dependencies, builds the .app, and places it under
~/.claude/apps/Claude Code Notifier.app. Override the destination with
CCN_APP_DIR=/some/other/path ./install.sh.
The first time a notification fires, macOS will ask for notification permission. Approve it once and future notifications will show immediately.
Note: running both installation methods at once places two
.appcopies with the same bundle identifier on your system, and macOS can become inconsistent about which instance receives the click-through. Pick one and remove the other.
# Simple test
open "$HOME/.claude/apps/Claude Code Notifier.app" \
--args "Hello from claude-code-notifier" "Claude Code" "default"Arguments: message, title, sound-name-or-label, activate-bundle-id.
All are optional except message. Empty values fall back to the configuration
file.
open "$HOME/.claude/apps/Claude Code Notifier.app" \
--args "<message>" "<title>" "<sound>" "<activate-bundle-id>"When the app is launched with an empty message (which is also what happens
when a user clicks an already-delivered notification), it activates the
configured activate-bundle-id and exits.
Sound is resolved in this order:
- If the given value matches a key in
config.jsonundersounds, the mapped sound name is used (e.g.permission→Purr). - Otherwise the value is taken as a literal macOS system sound name (e.g.
Glass,Hero,Funk) after allowlist validation (^[A-Za-z0-9_]+$). - Otherwise the
defaultentry inconfig.jsonis used.
Bundle ID is validated against ^[a-zA-Z0-9.\-]+$ and limited to 255 chars.
On first launch, the following file is created with defaults:
~/.config/claude-code-notifier/config.json
{
"activateBundleID": "com.microsoft.VSCode",
"defaultTitle": "Claude Code",
"sounds": {
"default": "Glass",
"idle": "Glass",
"permission": "Purr"
}
}Sound names are the stems of files in /System/Library/Sounds (without
.aiff). Typical values: Glass, Hero, Funk, Purr, Pop, Ping,
Blow, Bottle, Frog, Morse, Sosumi, Submarine, Tink.
The third positional argument to the notifier is a label. It is resolved in this order:
- If it matches a key under
soundsinconfig.json, the mapped sound is used. For examplepermission→Purr. - Otherwise it is treated as a literal sound name after allowlist check.
- If still unresolved,
sounds.defaultis used as the fallback.
This indirection lets you change the sound for an event type without touching
every settings.json hook. idle and permission are just the default label
names; you can rename them or add your own.
Run ./install.sh, paste the hook snippet from the Integration with Claude
Code section into ~/.claude/settings.json.
permission events play Purr, idle events play Glass. Done.
You want a softer sound for permission prompts. Edit config.json:
{
"sounds": {
"default": "Glass",
"idle": "Glass",
"permission": "Pop"
}
}The hook configuration in settings.json does not change. The next time a
permission prompt fires, you hear Pop instead of Purr.
You want a distinct sound when a build finishes. Pick a new label (e.g.
build_done) and add it to config.json:
{
"sounds": {
"default": "Glass",
"idle": "Glass",
"permission": "Purr",
"build_done": "Hero"
}
}Call the notifier with the new label:
open "$HOME/.claude/apps/Claude Code Notifier.app" \
--args "Build finished" "My Project" "build_done"Or wire it into Claude Code settings.json as a hook for whatever event you
like (e.g. a post-build script). The notifier does not care where the label
comes from; it just looks it up in config.json.
Change activateBundleID in config.json:
{
"activateBundleID": "com.todesktop.230313mzl4w4u92"
}Now the click-through target of every notification is Cursor. Common IDE bundle identifiers:
| IDE | Bundle ID |
|---|---|
| Visual Studio Code | com.microsoft.VSCode |
| VS Code Insiders | com.microsoft.VSCodeInsiders |
| Cursor | com.todesktop.230313mzl4w4u92 |
| Windsurf | com.exafunction.windsurf |
| JetBrains IntelliJ IDEA | com.jetbrains.intellij |
| Terminal.app | com.apple.Terminal |
| iTerm2 | com.googlecode.iterm2 |
If you installed via brew install saiso/tap/claude-code-notifier, replace
$HOME/.claude/apps/ in the snippet below with
$(brew --prefix)/opt/claude-code-notifier/libexec/. The same snippet is also
printed by Homebrew's caveats after brew install.
Add the following to ~/.claude/settings.json under hooks:
{
"hooks": {
"Notification": [
{
"matcher": "idle_prompt",
"hooks": [
{
"type": "command",
"command": "open \"$HOME/.claude/apps/Claude Code Notifier.app\" --args \"返信待ちです\" \"\" \"idle\""
}
]
},
{
"matcher": "permission_prompt",
"hooks": [
{
"type": "command",
"command": "open \"$HOME/.claude/apps/Claude Code Notifier.app\" --args \"コマンド確認が必要です\" \"\" \"permission\""
}
]
}
]
}
}The empty title slot lets config.json decide; the third argument is a
label that resolves to a sound per config.json.
The Claude Code VS Code extension currently does not fire Notification
event hooks when a permission dialog appears (Anthropic
claude-code#8985,
open as of 2026-04-28). Terminal launches still fire Notification hooks
correctly; this regression only affects the VS Code extension.
Until the upstream fix ships, you can route notifications through the
PreToolUse event instead. The trade-off: the notifier fires on every
Bash invocation — including auto-allowed read-only commands — so it is
noisier than the dedicated Notification event would be.
Save the following script (any path works; the example uses
~/.config/claude-code-notifier/hooks/):
#!/bin/bash
# Workaround for anthropics/claude-code#8985: VS Code extension does not
# fire Notification event hooks. Route through PreToolUse Bash matcher.
INPUT=$(cat)
TOOL=$(echo "$INPUT" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('tool_name',''))" 2>/dev/null)
[ "$TOOL" = "Bash" ] || exit 0
open "$HOME/.claude/apps/Claude Code Notifier.app" \
--args "Command requires confirmation" "" "permission" >/dev/null 2>&1 &
exit 0mkdir -p ~/.config/claude-code-notifier/hooks
# paste the script above into ~/.config/claude-code-notifier/hooks/notify-on-bash-pre.sh
chmod +x ~/.config/claude-code-notifier/hooks/notify-on-bash-pre.shIf you installed via Homebrew, replace $HOME/.claude/apps/ inside the
script with $(brew --prefix)/opt/claude-code-notifier/libexec/.
Add a PreToolUse entry to your .claude/settings.json alongside the
regular Notification hooks:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash $HOME/.config/claude-code-notifier/hooks/notify-on-bash-pre.sh"
}
]
}
]
}
}When Anthropic ships a fix for
#8985, remove
this PreToolUse entry. Leaving it in place after the upstream fix lands
will cause the notifier to fire twice per permission dialog (once via
Notification, once via PreToolUse).
On first launch macOS presents an approval dialog. If you dismissed it, open System Settings → Notifications, find "Claude Code Notifier", and enable notifications. If the entry is missing, reset the permission and try again:
tccutil reset Notifications io.github.saiso.claude-code-notifier
open "$HOME/.claude/apps/Claude Code Notifier.app" --args "test" "" "default"Quit Notification Center so the icon cache refreshes:
killall usernotificationsdCLAUDE_CODE_NOTIFIER_DEBUG=1 open "$HOME/.claude/apps/Claude Code Notifier.app" \
--args "debug test" "" "default"
tail -f ~/.claude/apps/notifier.log./uninstall.shThis removes the application bundle and (with confirmation) the configuration
directory at ~/.config/claude-code-notifier/.
To revoke the notification permission entirely:
tccutil reset Notifications io.github.saiso.claude-code-notifierIssues and pull requests are welcome. See CONTRIBUTING.md for guidelines and CODE_OF_CONDUCT.md for community expectations. Please open an issue before starting substantial work so the scope can be agreed on.
To report a security issue, follow the private disclosure process in SECURITY.md. Please do not open public issues for vulnerabilities.
MIT. See LICENSE.
Claude and Claude Code are trademarks of Anthropic, PBC. This project is not affiliated with, endorsed by, or sponsored by Anthropic. See TRADEMARKS.md for third-party trademark notices and attributions for bundled assets (Heroicons, Apple SF, etc.).
Made by saiso.
Blog and other projects: https://saiso.jp