I’ve been using Claude Code a lot lately, and one thing that kept bugging me was missing when it needed my input. I’d be in another tab, another window, deep in other projects, and Claude’s just sitting there waiting for me to approve a tool call or answer a question.
Two things I wanted out of this:
- The window where Claude Code is waiting pops to the front automatically
- A sound fires so I hear it even if I’m looking elsewhere
The whole thing ended up being a Claude Code hook, one 40 line shell script, and a Star Wars fanfare generated from pure Python. No installs. No app. No brew. No npm. Just macOS, osascript, and afplay.
The Hook
Claude Code has a hooks system that runs shell commands on lifecycle events. The Notification hook fires whenever Claude needs your attention. Tool approvals, questions, task completion. Add this to your global settings at ~/.claude/settings.json:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "~/.claude/notify.sh"
}
]
}
]
}
}
That is the only change you make to settings.json. Everything else lives in the shell script.
The Script
Save this as ~/.claude/notify.sh and chmod +x it:
#!/bin/bash
# Claude Code Notification hook: bring the host app window to the front and play the fanfare.
# Walks the process tree up from $PPID to find the GUI host app (Zed, Terminal, iTerm, etc.).
pid=$PPID
host_app_path=""
while [ -n "$pid" ] && [ "$pid" != "1" ] && [ "$pid" != "0" ]; do
comm=$(ps -o comm= -p "$pid" 2>/dev/null)
if [[ "$comm" == *.app/* ]]; then
host_app_path="${comm%.app/*}.app"
break
fi
pid=$(ps -o ppid= -p "$pid" 2>/dev/null | tr -d ' ')
done
app_name="Zed"
if [ -n "$host_app_path" ]; then
app_name=$(basename "$host_app_path" .app)
fi
# Terminal.app gets per-window targeting via TTY matching. Everything else gets app-level focus.
if [ "$app_name" = "Terminal" ]; then
target_tty=$(tty 2>/dev/null)
[ "$target_tty" = "not a tty" ] && target_tty=""
if [ -n "$target_tty" ]; then
osascript <<OSASCRIPT
tell application "Terminal"
activate
repeat with w in windows
repeat with t in tabs of w
if tty of t is "$target_tty" then
set selected tab of w to t
set index of w to 1
return
end if
end repeat
end repeat
end tell
OSASCRIPT
else
osascript -e 'tell application "Terminal" to activate'
fi
else
osascript -e "tell application \"$app_name\" to activate"
fi
afplay ~/.claude/star_wars.wav
Three things are doing the work here:
- The PPID walk. Starting from the parent process of this script, it walks up the process tree until it hits something running inside a
.appbundle. That is your host app, whether it is Zed, Terminal, iTerm, Warp, or anything else. No hardcoded assumptions. osascript ... activate. Brings the host app to the foreground. This is how the window pops up. For most apps that is as deep as macOS lets you go.- Terminal TTY targeting. Terminal.app is one of the few terminal emulators Apple ships with a full AppleScript dictionary that exposes
ttyon each tab. If the hook is running under Terminal.app, the script captures the currenttty, then loops every window and tab, matches ontty, and raises the exact window Claude Code is attached to. If you have four Terminal windows open and Claude is in window three, window three comes to the front, not window one.
Zed is not AppleScript scriptable, so you get app level focus and that is it. iTerm2, Ghostty, Kitty, Warp, and friends each have their own story. If you want yours added, drop a comment below and I will work it in.
But Why Use a Default Sound When You Can Have the Star Wars Theme?
Here’s where it got fun. Instead of a boring system ping, I put together the Star Wars main title fanfare, the iconic “da da da DAAAA da,” as a WAV file using nothing but Python’s standard library. No ffmpeg, no sox, no downloads. Just math.sin() and the wave module.
Here’s the script that generates it:
import wave, struct, math
SAMPLE_RATE = 44100
AMPLITUDE = 0.6
def tone(freq, duration):
n = int(SAMPLE_RATE * duration)
fade = int(SAMPLE_RATE * 0.01)
samples = []
for i in range(n):
env = 1.0
if i < fade:
env = i / fade
elif i > n - fade:
env = (n - i) / fade
val = (
math.sin(2 * math.pi * freq * i / SAMPLE_RATE) * 0.7
+ math.sin(2 * math.pi * freq * 2 * i / SAMPLE_RATE) * 0.2
+ math.sin(2 * math.pi * freq * 3 * i / SAMPLE_RATE) * 0.1
)
samples.append(int(val * env * AMPLITUDE * 32767))
return samples
BPM = 108
q = 60 / BPM
de = q * 0.75
s = q * 0.25
h = q * 2
triplet = q / 3
# First 19 notes of the Star Wars main title fanfare, ~9 seconds
notes = [
(233.08, triplet), (233.08, triplet), (233.08, triplet),
(349.23, h), (523.25, h),
(466.16, triplet), (440.00, triplet), (392.00, triplet),
(698.46, h), (523.25, q),
(466.16, triplet), (440.00, triplet), (392.00, triplet),
(698.46, h), (523.25, q),
(466.16, triplet), (440.00, triplet), (466.16, triplet),
(392.00, h),
]
samples = []
gap = [0] * int(SAMPLE_RATE * 0.02)
for freq, dur in notes:
samples.extend(tone(freq, dur - 0.02))
samples.extend(gap)
with wave.open("star_wars.wav", "w") as w:
w.setnchannels(1)
w.setsampwidth(2)
w.setframerate(SAMPLE_RATE)
w.writeframes(struct.pack(f"<{len(samples)}h", *samples))
Run it, drop the WAV in ~/.claude/, and you’re done.
How It Works
The script builds audio from scratch:
- Each note is a sine wave at the correct frequency (e.g., Bb3 = 233.08 Hz, F4 = 349.23 Hz)
- Harmonics are layered on top (2nd and 3rd overtones) to make it sound richer than a plain beep
- A quick fade in/out on each note prevents clicking artifacts
- The notes follow the actual Star Wars main title melody in Bb major at 108 BPM
- It all gets packed into a standard WAV file using Python’s built-in
waveandstructmodules
The Result
Now every time Claude Code needs my attention, the window it is running in pops to the front and the Star Wars opening fanfare plays. No guessing which terminal. No clicking anything. No brew installs, no npm packages, no dependencies. Just Python, bash, osascript, and afplay.
It started as “how do I get a notification sound?” and ended as a custom composed Star Wars theme generated from sine waves plus a 40 line shell script that routes focus back into the right window.
Do Not Disturb and the Lock Screen
A couple of behaviors worth knowing about. If the Mac is asleep, nothing fires at all because afplay needs the CPU awake. When you wake the Mac, nothing is queued. If the Mac is locked but not asleep, the fanfare plays to completion but osascript ... activate cannot raise windows through the lock screen. And Do Not Disturb or Focus modes do not affect this setup at all, because there is no notification banner involved. The fanfare plays and osascript activates the app the moment you unlock.
Quick Reference
| What | Where |
|---|---|
| Claude Code settings | ~/.claude/settings.json |
| Hook script | ~/.claude/notify.sh |
| Sound file | ~/.claude/star_wars.wav |
| Audio generator | ~/.claude/star_wars.py |
| Test the sound | afplay ~/.claude/star_wars.wav |
| Test the full flow | ~/.claude/notify.sh |
Want a different tune? Just change the frequencies and durations in the notes array of the Python generator. The whole melody is a list of (frequency_hz, duration_seconds) tuples. Go nuts.
Star Wars is a trademark of Lucasfilm Ltd. This post is for educational purposes and is not affiliated with or endorsed by Lucasfilm or Disney.
Comments
No comments yet. Be the first to share your thoughts.
Leave a Comment
Commented before? to skip the form fields.
Sign in
Enter the 6-digit code sent to
We sent a 6-digit code to