When Claude Code needs your attention — whether it’s waiting on a permission request, finished a long task, or just has something to tell you — it’s easy to miss if you’ve switched to another window. Here’s how to wire up native macOS notifications so you never have to babysit the terminal again.

The notification script

Save this script to ~/.claude/notify.sh:

#!/bin/bash
TITLE="$1"
MESSAGE="$2"
 
# Check env var first (works if Claude was launched from the shell directly)
if [ "$TERM_PROGRAM" = "vscode" ]; then
  APP="com.microsoft.VSCode"
elif [ "$TERM_PROGRAM" = "WezTerm" ]; then
  APP="com.github.wez.wezterm"
elif [ "$TERM_PROGRAM" = "iTerm.app" ]; then
  APP="com.googlecode.iterm2"
else
  # Walk up the full ancestor chain, not just $PPID
  APP="com.apple.Terminal"  # default
  PID=$$
  for _ in $(seq 1 10); do
    PID=$(ps -p "$PID" -o ppid= 2>/dev/null | tr -d ' ')
    [ -z "$PID" ] || [ "$PID" -le 1 ] && break
    PROC=$(ps -p "$PID" -o comm= 2>/dev/null | xargs basename 2>/dev/null)
    case "$PROC" in
      *Code*)       APP="com.microsoft.VSCode";     break ;;
      *wezterm*)    APP="com.github.wez.wezterm";   break ;;
      *iTerm*)      APP="com.googlecode.iterm2";    break ;;
      *Warp*)       APP="dev.warp.Warp-Stable";     break ;;
      *Alacritty*)  APP="io.alacritty";             break ;;
      *kitty*)      APP="net.kovidgoyal.kitty";     break ;;
    esac
  done
fi
 
terminal-notifier -title "$TITLE" -message "$MESSAGE" -activate "$APP"

The script detects which terminal emulator launched Claude Code — first by checking $TERM_PROGRAM, then by walking up the process ancestry — so that clicking a notification focuses the right app. Make it executable:

chmod +x ~/.claude/notify.sh

You’ll also need terminal-notifier if you don’t have it:

brew install terminal-notifier

Hooking into Claude Code events

Add the following to your ~/.claude/settings.json under the hooks key:

"hooks": {
  "PermissionRequest": [
    {
      "matcher": "",
      "hooks": [
        {
          "type": "command",
          "command": "bash $HOME/.claude/notify.sh \"🔔 Claude Code\" \"Claude needs your input\""
        }
      ]
    }
  ],
  "Notification": [
    {
      "matcher": "",
      "hooks": [
        {
          "type": "command",
          "command": "bash $HOME/.claude/notify.sh \"🔔 Claude Code\" \"Claude needs your attention\""
        }
      ]
    }
  ],
  "Stop": [
    {
      "matcher": "",
      "hooks": [
        {
          "type": "command",
          "command": "bash $HOME/.claude/notify.sh \"✅ Claude Code\" \"Task completed\""
        }
      ]
    }
  ]
}

Three events are covered: PermissionRequest fires when Claude is blocked waiting for you to approve a tool call, Notification catches any other attention-needed signal, and Stop lets you know when the task is fully done. Now you can freely switch away while Claude works and get a tap on the shoulder the moment something needs you.