Like Elena waiting for Damon to become "good," I was staring at my Claude Code terminal, waiting for it to finish my request.
One day, I was so fed up with waiting that I asked myself, "What could make this better?"
The first thing that came to mind was...
C A T S. 🐈
But how?
Then my inner Claude ran /research inside my brain, and that's how this idea was born.
I needed to fulfill two requirements:
🐈⬛ notify me when Claude had finished its work;
🐈 notify me when it needed my permission and was blocked waiting for it.
But I also wanted to make my workflow less stressful 🥹.
Here's how to add sound hooks to Claude Code using nothing more than a couple of shell scripts and two .mp3 files.
How It Works
Two hook events in ~/.claude/settings.json trigger shell scripts:
| Event | Script | When it fires |
|---|---|---|
Stop |
play-success.sh |
Claude finishes its turn (response complete) |
PermissionRequest |
play-permission.sh |
Claude is waiting for your approval |
Both run with "async": true, so the sound plays in the background without delaying Claude's response or the permission prompt.
File Structure
~/.claude/
settings.json
hooks/
_play.sh <- shared helper (source this, don't call directly)
play-success.sh
play-permission.sh
sounds/
success.mp3
permission.mp3
Step 1 — Add your .mp3 files
Place sound files in ~/.claude/hooks/sounds/. Missing files are silently skipped — no errors.
💡 Tip: I use two different meows: a happy, purring one when Claude finishes successfully, and a slightly worried one when it's waiting for my approval.
Step 2 — Create _play.sh (shared helper)
#!/usr/bin/env bash
SOUNDS_DIR="$HOME/.claude/hooks/sounds"
play_sound() {
local name="$1"
local file="$SOUNDS_DIR/${name}.mp3"
[ -f "$file" ] || return 0
if command -v afplay &>/dev/null; then
# macOS
afplay "$file" &>/dev/null &
elif command -v powershell.exe &>/dev/null; then
# Windows (Git Bash) — convert /c/Users/... to C:/Users/...
local win_path
win_path=$(echo "$file" | sed 's|^/\([a-zA-Z]\)/|\1:/|')
win_path="${win_path^}"
powershell.exe -NoProfile -NonInteractive -c "
Add-Type -AssemblyName PresentationCore
\$m = New-Object System.Windows.Media.MediaPlayer
\$m.Open([uri]'file:///$win_path')
\$m.Play()
Start-Sleep -Milliseconds 500
while (\$m.NaturalDuration -eq [System.Windows.Duration]::Automatic) {
Start-Sleep -Milliseconds 100
}
Start-Sleep -Milliseconds \$m.NaturalDuration.TimeSpan.TotalMilliseconds
\$m.Stop()
" &>/dev/null &
fi
}
Step 3 — Create hook scripts
play-success.sh — fires when Claude finishes a turn:
#!/usr/bin/env bash
source "$HOME/.claude/hooks/_play.sh"
play_sound "success"
play-permission.sh — fires when Claude is waiting for your approval:
#!/usr/bin/env bash
source "$HOME/.claude/hooks/_play.sh"
play_sound "permission"
Step 4 — Wire up in ~/.claude/settings.json
{
"hooks": {
"Stop": [
{
"matcher": ".*",
"hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/play-success.sh", "async": true }]
}
],
"PermissionRequest": [
{
"matcher": ".*",
"hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/play-permission.sh", "async": true }]
}
]
}
}
Testing
Run scripts directly to verify audio works before wiring into Claude:
bash ~/.claude/hooks/play-success.sh
bash ~/.claude/hooks/play-permission.sh
Then trigger each event from Claude: finish any prompt to hear success.mp3, or trigger a permission request (e.g. ask Claude to run a shell command in auto-approve mode) to hear permission.mp3.
As a result, I can freely switch between tabs while Claude is running commands without worrying that the process has paused because it's waiting for my attention.
Unlike Elena, I don't have to spend seasons waiting anymore💁♀️
Top comments (0)