Claude Code Notifications: Stop Babysitting Your AI

You ask Claude Code to build something. You switch to email. 20 minutes later you check back. It finished 18 minutes ago.

Claude Code hooks fix this. A Stop hook triggers when Claude finishes. A Notification hook triggers when it needs input. Wire them to a notification script (sound + popup, any platform).

Here’s the fix, for Mac, Windows and Linux.

Tip

Works with other agents too. Codex CLI, Gemini CLI, OpenCode and other terminal coding assistants that support hooks can call the same script.

Tip

Let Claude set it up. Copy this page’s URL, paste it into Claude Code, and ask it to set this up for you. It will read the guide and handle the rest.

What You’re Setting Up

A system notification that fires the moment Claude Code finishes or needs input:

  • Project context: the notification shows which repo
  • Distinct sounds: a different chime for “done” and “needs input”
  • Zero polling: hooks trigger automatically
  • Any platform: Mac, Windows, Linux

How It Works

Claude Code has a hook system. Two hooks matter here:

  • Stop: fires when Claude finishes a task
  • Notification: fires when Claude needs your input

Each hook runs a shell command. We point them at a notification script.

Add this to ~/.claude/settings.json:

{
  "hooks": {
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "~/.local/bin/notify.sh 'Task completed' complete"
          }
        ]
      }
    ],
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "~/.local/bin/notify.sh 'Needs input' input"
          }
        ]
      }
    ]
  }
}

That’s the wiring. Now you need the script it calls.

The Notification Script

Pick your platform:

Mac

Option A: No dependencies

mkdir -p ~/.local/bin && cat > ~/.local/bin/notify.sh << 'EOF'
#!/bin/bash
MESSAGE="${1:-Task completed}"
SOUND_TYPE="${2:-default}"
PROJECT=""
git rev-parse --is-inside-work-tree &>/dev/null && \
  PROJECT=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)")
TITLE="${PROJECT:-Claude Code}"
case "$SOUND_TYPE" in
  input)    SOUND="Basso" ;;
  complete) SOUND="Glass" ;;
  *)        SOUND="Pop" ;;
esac
osascript -e "display notification \"$MESSAGE\" with title \"$TITLE\" sound name \"$SOUND\""
EOF
chmod +x ~/.local/bin/notify.sh

Option B: terminal-notifier (richer notifications, shows in Notification Center)

brew install terminal-notifier
mkdir -p ~/.local/bin && cat > ~/.local/bin/notify.sh << 'EOF'
#!/bin/bash
MESSAGE="${1:-Task completed}"
SOUND_TYPE="${2:-default}"
PROJECT=""
git rev-parse --is-inside-work-tree &>/dev/null && \
  PROJECT=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)")
TITLE="${PROJECT:-Claude Code}"
case "$SOUND_TYPE" in
  input)    SOUND="Basso" ;;
  complete) SOUND="Glass" ;;
  *)        SOUND="default" ;;
esac
terminal-notifier -title "$TITLE" -message "$MESSAGE" -sound "$SOUND"
EOF
chmod +x ~/.local/bin/notify.sh

Windows / WSL2

WSL2’s notify-send doesn’t reach Windows. This calls PowerShell directly, no installs required:

mkdir -p ~/.local/bin && cat > ~/.local/bin/notify.sh << 'EOF'
#!/bin/bash
MESSAGE="${1:-Task completed}"
SOUND_TYPE="${2:-default}"
PROJECT=""
git rev-parse --is-inside-work-tree &>/dev/null && \
  PROJECT=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)")
TITLE="${PROJECT:-Claude Code}"
MESSAGE_ESC="${MESSAGE//\"/\`\"}"
TITLE_ESC="${TITLE//\"/\`\"}"
case "$SOUND_TYPE" in
  input)    WAV="C:\\Windows\\Media\\Windows Exclamation.wav" ;;
  complete) WAV="C:\\Windows\\Media\\tada.wav" ;;
  *)        WAV="C:\\Windows\\Media\\chimes.wav" ;;
esac
/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -NoProfile -NonInteractive -Command "
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
\$balloon = New-Object System.Windows.Forms.NotifyIcon
\$balloon.Icon = [System.Drawing.SystemIcons]::Information
\$balloon.BalloonTipIcon = [System.Windows.Forms.ToolTipIcon]::Info
\$balloon.BalloonTipTitle = '$TITLE_ESC'
\$balloon.BalloonTipText = '$MESSAGE_ESC'
\$balloon.Visible = \$true
\$balloon.ShowBalloonTip(5000)
\$player = New-Object System.Media.SoundPlayer '$WAV'
\$player.PlaySync()
Start-Sleep -Milliseconds 500
\$balloon.Dispose()
" 2>/dev/null &
exit 0
EOF
chmod +x ~/.local/bin/notify.sh

Windows (Native)

Running Claude Code in PowerShell (not WSL)? Use a PowerShell script:

# Save to: $env:USERPROFILE\.claude\hooks\notify.ps1
param($Message = "Task completed", $SoundType = "default")

$Project = "Claude Code"
try {
    $gitRoot = git rev-parse --show-toplevel 2>$null
    if ($gitRoot) { $Project = Split-Path $gitRoot -Leaf }
} catch {}

$Wav = switch ($SoundType) {
    "input"    { "C:\Windows\Media\Windows Exclamation.wav" }
    "complete" { "C:\Windows\Media\tada.wav" }
    default    { "C:\Windows\Media\chimes.wav" }
}

Add-Type -AssemblyName System.Windows.Forms, System.Drawing
$balloon = New-Object System.Windows.Forms.NotifyIcon
$balloon.Icon = [System.Drawing.SystemIcons]::Information
$balloon.BalloonTipTitle = $Project
$balloon.BalloonTipText = $Message
$balloon.Visible = $true
$balloon.ShowBalloonTip(5000)

(New-Object System.Media.SoundPlayer $Wav).PlaySync()
Start-Sleep -Milliseconds 500
$balloon.Dispose()

Hook command for native Windows:

"command": "powershell -ExecutionPolicy Bypass -File ~/.claude/hooks/notify.ps1 'Task completed' complete"

Linux

Uses notify-send. Install if missing:

# Ubuntu/Debian
sudo apt install libnotify-bin

# Fedora
sudo dnf install libnotify

# Arch
sudo pacman -S libnotify
mkdir -p ~/.local/bin && cat > ~/.local/bin/notify.sh << 'EOF'
#!/bin/bash
MESSAGE="${1:-Task completed}"
SOUND_TYPE="${2:-default}"
PROJECT=""
git rev-parse --is-inside-work-tree &>/dev/null && \
  PROJECT=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)")
TITLE="${PROJECT:-Claude Code}"
case "$SOUND_TYPE" in
  input)    URGENCY="critical" ;;
  *)        URGENCY="normal" ;;
esac
notify-send -u "$URGENCY" "$TITLE" "$MESSAGE"
# Sound (if PulseAudio available)
command -v paplay &>/dev/null && {
  case "$SOUND_TYPE" in
    input)    paplay /usr/share/sounds/freedesktop/stereo/dialog-warning.oga & ;;
    complete) paplay /usr/share/sounds/freedesktop/stereo/complete.oga & ;;
  esac
}
EOF
chmod +x ~/.local/bin/notify.sh

Test It

# Should see popup + hear sound
~/.local/bin/notify.sh "Build finished" complete
~/.local/bin/notify.sh "Need approval" input

Working? Restart Claude Code. The hooks only load on startup.

Customize Sounds

Windows:

# Browse options
ls /mnt/c/Windows/Media/*.wav

# Preview
/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -Command \
  "(New-Object System.Media.SoundPlayer 'C:\Windows\Media\tada.wav').PlaySync()"

Good ones: tada.wav (victory), chimes.wav (subtle), Windows Exclamation.wav (urgent).

Mac:

ls /System/Library/Sounds/
afplay /System/Library/Sounds/Glass.aiff

Good ones: Glass, Hero, Funk, Basso.

Note

Custom sounds: you can use any .wav (Windows) or .aiff (Mac) file: game sounds, movie clips, whatever. Just point the script to the file path. Keep custom sounds in your home directory rather than dumping them into C:\Windows\Media, so you don’t bloat a system folder.

Going Further

The script is just bash. Add whatever you want:

Phone notifications via ntfy.sh:

curl -s -d "$MESSAGE" "ntfy.sh/your-secret-topic" &>/dev/null &

Slack:

curl -s -X POST -H 'Content-type: application/json' \
  --data "{\"text\":\"$TITLE: $MESSAGE\"}" \
  "$SLACK_WEBHOOK" &>/dev/null &

Flash your office lights (Home Assistant):

curl -s -X POST -H "Authorization: Bearer $HA_TOKEN" \
  -d '{"entity_id": "light.office"}' \
  "http://homeassistant.local:8123/api/services/light/turn_on" &>/dev/null &

Troubleshooting

No sound (Windows)?

  • Volume not muted?
  • Test directly: /mnt/c/.../powershell.exe -Command "(New-Object System.Media.SoundPlayer 'C:\Windows\Media\chimes.wav').PlaySync()"

No popup (Windows)?

  • Settings → System → Notifications → On
  • Focus Assist blocks notifications when active

Nothing (Mac)?

  • Test: afplay /System/Library/Sounds/Glass.aiff
  • System Settings → Notifications → Terminal (or terminal-notifier) must allow alerts

Mac approach via Boris Buliga.

Scripts available on GitHub.

Notifications tell you when Claude needs you; our guide to reusable Claude Code project instructions shows how to stop repeating the same setup every session.

Comments

No comments yet.