Never miss a Claude Code prompt again. Get native macOS notifications with sound when Claude Code asks for permission, completes a task, or has a question β even when you're working in another window or app.
When using Claude Code, it sometimes needs your permission to run commands, finishes a long task while you're away, or asks you a question. If you're working across multiple windows, projects, or monitors, you can easily miss these prompts β leaving Claude waiting and breaking your flow.
A lightweight hook that sends native macOS notifications for key Claude Code events. Click the notification to jump straight back to your editor.
Features:
- Permission requests β notified when Claude needs approval for Bash, Edit, Write, etc.
- Task completion β notified when Claude finishes working and waits for your input
- Questions β notified when Claude asks you a question
- Native macOS notification with distinct sounds per event type
- Click-to-focus: tapping the notification activates your editor
- Works even in Do Not Disturb mode
- Global setup β works across all your projects
- Minimal dependencies β just
terminal-notifierandjq
- macOS (uses Notification Center)
- Claude Code installed
- terminal-notifier β a CLI tool for macOS notifications
- jq β for parsing JSON input from the hook
curl -fsSL https://raw.githubusercontent.com/codewithmustafa/claude-code-notifier/master/install.sh | bashbrew install terminal-notifier jqmkdir -p ~/.claude/hooks
cat > ~/.claude/hooks/permission-notification.sh << 'EOF'
#!/bin/bash
# Claude Code Notifier
# https://github.com/codewithmustafa/claude-code-notifier
INPUT=$(cat)
EVENT=$(echo "$INPUT" | jq -r '.hook_event_name // "Unknown"')
case "$EVENT" in
PermissionRequest)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // "Unknown"')
TITLE="Claude Code"
MESSAGE="Permission requested: $TOOL_NAME"
SOUND="Pop"
;;
Notification)
NOTIFICATION_TYPE=$(echo "$INPUT" | jq -r '.notification_type // "unknown"')
case "$NOTIFICATION_TYPE" in
idle_prompt)
TITLE="Claude Code"
MESSAGE="Task completed β waiting for your input"
SOUND="Glass"
;;
elicitation_dialog)
TITLE="Claude Code"
MESSAGE="Claude has a question for you"
SOUND="Ping"
;;
*)
TITLE="Claude Code"
MESSAGE=$(echo "$INPUT" | jq -r '.message // "Notification"')
SOUND="Ping"
;;
esac
;;
*)
TITLE="Claude Code"
MESSAGE="Notification from Claude Code"
SOUND="Ping"
;;
esac
/opt/homebrew/bin/terminal-notifier \
-title "$TITLE" \
-message "$MESSAGE" \
-sound "$SOUND" \
-ignoreDnD
exit 0
EOF
chmod +x ~/.claude/hooks/permission-notification.shOpen ~/.claude/settings.json and add the hooks section:
{
"hooks": {
"PermissionRequest": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/permission-notification.sh"
}
]
}
],
"Notification": [
{
"matcher": "idle_prompt|elicitation_dialog",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/permission-notification.sh"
}
]
}
]
}
}If you already have content in
settings.json, merge thehookskey into your existing config.
# Permission request notification
echo '{"hook_event_name": "PermissionRequest", "tool_name": "Bash"}' | bash ~/.claude/hooks/permission-notification.sh
# Task completed notification
echo '{"hook_event_name": "Notification", "notification_type": "idle_prompt"}' | bash ~/.claude/hooks/permission-notification.sh
# Question notification
echo '{"hook_event_name": "Notification", "notification_type": "elicitation_dialog"}' | bash ~/.claude/hooks/permission-notification.sh| Event | When | Notification | Sound |
|---|---|---|---|
| Permission request | Claude needs approval for a tool | "Permission requested: Bash" | Pop |
| Task completed | Claude finishes and waits for input | "Task completed β waiting for your input" | Glass |
| Question | Claude asks you a question | "Claude has a question for you" | Ping |
You can make the notification activate your editor when clicked. Add the -activate flag with your editor's bundle ID:
/opt/homebrew/bin/terminal-notifier \
-title "$TITLE" \
-message "$MESSAGE" \
-sound "$SOUND" \
-ignoreDnD \
-activate <BUNDLE_ID>| Editor | Bundle ID |
|---|---|
| Cursor | com.todesktop.230313mzl4w4u92 |
| VS Code | com.microsoft.VSCode |
| iTerm2 | com.googlecode.iterm2 |
| Terminal.app | com.apple.Terminal |
| Warp | dev.warp.Warp-Stable |
| Alacritty | org.alacritty |
| Kitty | net.kovidgoyal.kitty |
| Hyper | co.zeit.hyper |
| WezTerm | com.github.wez.wezterm |
| Windsurf | com.exafunction.windsurf |
Find your editor's bundle ID:
osascript -e 'id of app "Your App Name"'
Replace Ping or Glass with any macOS system sound:
Basso, Blow, Bottle, Frog, Funk, Glass, Hero, Morse, Ping, Pop, Purr, Sosumi, Submarine, Tink
Instead of matching all permission requests ("*"), you can target specific tools:
{
"hooks": {
"PermissionRequest": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/permission-notification.sh"
}
]
}
]
}
}For more detailed notifications showing what Claude wants to run:
#!/bin/bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // "Unknown"')
TOOL_INPUT=$(echo "$INPUT" | jq -r '.tool_input.command // .tool_input.file_path // "operation"')
# Truncate long commands
if [ ${#TOOL_INPUT} -gt 80 ]; then
TOOL_INPUT="${TOOL_INPUT:0:77}..."
fi
/opt/homebrew/bin/terminal-notifier \
-title "Claude Code: $TOOL_NAME" \
-message "$TOOL_INPUT" \
-sound Ping \
-ignoreDnD
exit 0macOS restricts display notification (osascript) to the parent app's notification permissions. Most terminal emulators have notifications disabled by default, so osascript notifications silently fail. terminal-notifier is a standalone app with its own notification permission, making it reliable regardless of your terminal setup.
-
Check terminal-notifier is installed:
which terminal-notifier
If not found, run
brew install terminal-notifier. -
Check notification permissions: Go to System Settings > Notifications > terminal-notifier and make sure notifications are enabled.
-
Test the script directly:
echo '{"hook_event_name": "PermissionRequest", "tool_name": "Test"}' | bash ~/.claude/hooks/permission-notification.sh
-
Check terminal-notifier path: The script assumes
/opt/homebrew/bin/terminal-notifier(Apple Silicon). For Intel Macs, the path is/usr/local/bin/terminal-notifier. Update the script accordingly.
- Check that your Mac's volume is not muted.
- Verify the sound name is valid (see Customization).
- Check System Settings > Notifications > terminal-notifier and ensure "Play sound for notifications" is on.
- Verify the bundle ID is correct:
osascript -e 'id of app "Cursor"' - Some apps may require the notification to be clicked within a few seconds.
Claude Code supports hooks β shell commands that run in response to specific events. This tool uses two hooks:
PermissionRequestfires whenever Claude Code shows a permission dialogNotificationfires when Claude Code needs your attention (task done, question asked)
Both hooks call the same script, which reads the JSON input, determines the event type, and sends an appropriate macOS notification via terminal-notifier.
Claude Code event fires
β
PermissionRequest or Notification hook
β
permission-notification.sh reads JSON
β
Determines event type & builds message
β
terminal-notifier sends macOS notification
β
You click β editor activates
MIT