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 correct way to solve this (in less than 5 minutes).

Pro Tip: This approach also works with Codex CLI, Gemini CLI, OpenCode, and other terminal-based AI coding assistants that support hook systems.
Ultra Pro Tip: Copy this URL, paste it into Claude Code, and ask it to set this up for you. It will read the guide and handle everything.

What You’re Setting Up

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

  • Project context — notification shows which repo
  • Distinct sounds — different chime for “done” vs “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.

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. Avoid dumping files into C:\Windows\Media though; keep custom sounds in your home directory to prevent system bloat.

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.

Author

Avatar for Will Mitchell
Will Mitchell

Will Mitchell is a serial entrepreneur and Founder of StartupBros. You can learn more about him at the Startupbros about page. If you have any questions or comments for him, just send an email or leave a comment!

Leave a Comment