Last active
December 3, 2025 15:52
-
-
Save cheapsteak/615124ecf7b4d8523730ba2b216cd75b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Claude Code notification hook for macOS | |
| # | |
| # Setup: | |
| # 1. Save this script to ~/.claude/hooks/notify.sh | |
| # 2. chmod +x ~/.claude/hooks/notify.sh | |
| # 3. brew install terminal-notifier | |
| # 4. System Settings → Privacy & Security → Accessibility: | |
| # - Add terminal-notifier (in /opt/homebrew/bin/ or /usr/local/bin/) | |
| # - Add Cursor and/or VSCode | |
| # 5. Add to ~/.claude/settings.json: | |
| # { | |
| # "hooks": { | |
| # "Notification": [ | |
| # { "matcher": "permission_prompt", "hooks": [{ "type": "command", "command": "~/.claude/hooks/notify.sh" }] }, | |
| # { "matcher": "idle_prompt", "hooks": [{ "type": "command", "command": "~/.claude/hooks/notify.sh" }] }, | |
| # { "matcher": "elicitation_dialog", "hooks": [{ "type": "command", "command": "~/.claude/hooks/notify.sh" }] } | |
| # ] | |
| # } | |
| # } | |
| # | |
| # Opt-out: export CLAUDE_QUIET=1 | |
| [[ -n "$CLAUDE_QUIET" ]] && exit 0 | |
| # Read JSON from stdin and extract fields | |
| input=$(cat) | |
| type=$(echo "$input" | grep -o '"notification_type":"[^"]*"' | cut -d'"' -f4) | |
| cwd=$(echo "$input" | grep -o '"cwd":"[^"]*"' | cut -d'"' -f4) | |
| # Get folder name and git branch for window matching | |
| folder_name=$(basename "$cwd") | |
| branch_name=$(git -C "$cwd" rev-parse --abbrev-ref HEAD 2>/dev/null) | |
| match_string="${branch_name:-$folder_name}" | |
| # Set message based on notification type | |
| case "$type" in | |
| permission_prompt) message="Permission needed" ;; | |
| idle_prompt) message="Waiting for input" ;; | |
| elicitation_dialog) message="Input required" ;; | |
| *) message="Needs attention" ;; | |
| esac | |
| # Detect which app spawned the terminal (VSCode or Cursor) | |
| app_bundle="${__CFBundleIdentifier:-com.microsoft.VSCode}" | |
| # Map bundle ID to app name for AppleScript | |
| case "$app_bundle" in | |
| com.todesktop.230313mzl4w4u92) app_name="Cursor" ;; | |
| com.microsoft.VSCode) app_name="Code" ;; | |
| *) app_name="Cursor" ;; | |
| esac | |
| # Check if the target window is already focused - skip notification if so | |
| is_focused=$(osascript -e " | |
| tell application \"System Events\" | |
| set frontApp to name of first application process whose frontmost is true | |
| if frontApp is \"$app_name\" then | |
| tell process \"$app_name\" | |
| if (count of windows) > 0 then | |
| set winName to name of front window | |
| if winName contains \"$match_string\" then | |
| return \"yes\" | |
| end if | |
| end if | |
| end tell | |
| end if | |
| return \"no\" | |
| end tell | |
| " 2>/dev/null) | |
| [[ "$is_focused" == "yes" ]] && exit 0 | |
| # Use terminal-notifier if available (supports click-to-focus), otherwise osascript | |
| if command -v terminal-notifier &>/dev/null; then | |
| # Create AppleScript to focus the specific window via System Events | |
| focus_script="tell application \"System Events\" | |
| tell process \"$app_name\" | |
| set frontmost to true | |
| repeat with w in windows | |
| if name of w contains \"$match_string\" then | |
| perform action \"AXRaise\" of w | |
| exit repeat | |
| end if | |
| end repeat | |
| end tell | |
| end tell" | |
| terminal-notifier -title "Claude Code" -message "$message [$match_string]" \ | |
| -execute "osascript -e '$focus_script'" | |
| else | |
| osascript -e "display notification \"$message\" with title \"Claude Code\"" | |
| fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment