Sound alerts for Claude Code, so you know when it needs you without watching the terminal.
An independent project, not affiliated with or endorsed by Anthropic. Nothing leaves your machine unless you turn on phone push. See NOTICE.
Claude Code is fast enough that the bottleneck is often your attention, not the model. You give it a task, switch to something else, and come back ten minutes later to find it has been sitting there the whole time waiting for you to approve one file edit. Or it finished eight minutes ago. Or it stopped on an error you never saw.
This wires a distinct sound to each point in Claude Code's lifecycle that is worth interrupting you for:
| Sound | Fires when |
|---|---|
| Soft chime | Claude finished the turn |
| Alert, twice, plus a desktop notification | Claude is waiting on you: a permission prompt, an idle prompt, or a subagent that needs input |
| Alarm, plus a desktop notification | You hit your usage limit |
| Alarm, plus a desktop notification | The turn died on some other API error |
| Rising chime, plus a phone push if enabled | Your usage limit has reset and you can work again |
| Soft chime, off by default | Your usage window rolled over, whether or not you hit the limit |
It installs into ~/.claude/settings.json, so it applies to every project and every terminal with no per-repository setup. It also stays quiet when you do not need it: see Options.
There are two ways in, and they produce the same thing on disk. The scripts below need no app; the desktop app needs no terminal.
Read the script first, then run it:
irm https://raw.githubusercontent.com/azyzex/ClaudeCodeSounds/main/install-claude-sound-alerts.ps1 -OutFile install.ps1
notepad install.ps1
powershell -ExecutionPolicy Bypass -File .\install.ps1If you would rather not read it, the one-liner is:
irm https://raw.githubusercontent.com/azyzex/ClaudeCodeSounds/main/install-claude-sound-alerts.ps1 | iexcurl -fsSL https://raw.githubusercontent.com/azyzex/ClaudeCodeSounds/main/install-claude-sound-alerts.sh -o install.sh
less install.sh
bash install.shEither way, restart Claude Code afterwards and run /hooks to confirm all five entries are listed.
The installer plays a test tone at the end. If you hear it, the audio path works.
Two files, both under ~/.claude:
claude-notify.ps1orclaude-notify.shis created. This is the script that actually picks a sound and plays it.claude-sounds/is created, holding the nine bundled alert sounds.settings.jsonis backed up with a timestamp, then the hook entries are added to it.claude-notify.confis created if absent. Your edits to it are never overwritten.
The installer merges rather than replaces. Any hooks you already have are left alone. It is safe to re-run: it strips its own entries before writing, so running it twice does not stack duplicates and give you overlapping chimes.
powershell -ExecutionPolicy Bypass -File .\install-claude-sound-alerts.ps1 -Uninstallbash install-claude-sound-alerts.sh --uninstallThis removes the notifier script and its own hook entries, and leaves the rest of your settings untouched. Timestamped backups are kept either way, so you can always restore by hand.
To silence the alerts temporarily without uninstalling, set "disableAllHooks": true in ~/.claude/settings.json.
Claude Code has a hooks system: you attach a command to a named point in its lifecycle and it runs deterministically, without the model having to decide to call it. These events matter here.
Stopfires at the end of every turn. No matcher, it always fires.Notificationfires when Claude wants something from you. It takes a matcher on notification type, so this setup listens forpermission_prompt,idle_prompt,agent_needs_input, andelicitation_dialog, and deliberately ignores the rest. You do not want a chime forauth_success.StopFailurefires when a turn ends on an error, and it takes a matcher on error type. One of the values israte_limit, which means hitting your usage limit can have its own distinct sound instead of being lumped in with every other failure. That is the alert that is genuinely hard to get any other way.StopFailureagain, matched against every remaining error type, for real failures.UserPromptSubmitrecords when a turn started, soStopcan tell a ten-minute turn from a four-second one. It plays nothing.
Two details make the difference between this being useful and being actively annoying.
async: true. Without it, Claude Code blocks for the full length of the audio clip at the end of every single turn. You feel it immediately.
Debouncing. Several of these events can fire inside the same second, and you get a stutter of overlapping sounds. The notifier keeps a timestamp in the temp directory and ignores anything that arrives within a couple of seconds of the last alert.
The installer asks about each option the first time you run it, and writes your answers to ~/.claude/claude-notify.conf. Same file, same option names on every platform. To change them later:
bash install-claude-sound-alerts.sh --configpowershell -ExecutionPolicy Bypass -File .\install-claude-sound-alerts.ps1 -ConfigureOr edit the file directly. The notifier re-reads it on every alert, so changes take effect immediately with no reinstall and no restart. The installer never overwrites an existing config.
| Option | Default | What it does |
|---|---|---|
MIN_SECONDS |
30 |
Stay silent when a turn finished faster than this. Short back-and-forth turns are the main source of alert fatigue. 0 disables the check. |
SUPPRESS_WHEN_FOCUSED |
1 |
Skip the alert when the terminal is already the focused window, on the basis that you are evidently already looking at it. |
PROJECT_PITCH |
1 |
Pick the finish sound from the working directory, so with several terminals open you can tell which project it was. |
SPEAK |
0 |
Read the alert aloud instead of playing a sound. Falls back to the normal chime if no working speech synthesiser is present. |
TOAST_ON_DONE |
0 |
Also raise a desktop notification when a turn merely finishes. |
DEBOUNCE_SECONDS |
2 |
Ignore repeat alerts for this long, so overlapping events do not stutter. |
ALWAYS_ALERT |
blocked,limit,error |
Kinds that ignore MIN_SECONDS and the focus check, because you want to know regardless. |
MUTE |
empty | Kinds to silence completely. |
QUIET_HOURS |
empty | Silence everything inside a window, for example 23:00-08:00. Windows that wrap past midnight work. |
RESPECT_DND |
1 |
Stay silent while the desktop is in do not disturb. Best effort: where it cannot be determined, the alert happens. |
ESCALATE_AFTER |
0 |
Nudge once more if a prompt goes unanswered this long. Needs the app running. |
MUTE_UNTIL |
empty | Silence everything until this epoch second, then expire. What the tray's "quiet for an hour" writes. |
NTFY_TOPIC |
empty | Push alerts to your phone. Empty disables it. See below. |
NTFY_SERVER |
https://ntfy.sh |
Where to push. Change it if you self-host. |
NTFY_ALERTS |
blocked,limit,error |
Which kinds to push. Pushing every finished turn to a phone gets old fast. |
SOUND_PACK |
default |
Which pack to use. See below. |
Each of the four kinds — DONE, BLOCKED, LIMIT, ERROR — has its own group.
| Option | What it does |
|---|---|
<KIND>_ENABLED |
0 turns that one kind off. |
<KIND>_VOLUME |
0-100. |
<KIND>_PATTERN |
How many times to play, optionally NxMS for the gap. 3x140 is three pulses 140ms apart. |
<KIND>_SOUND |
Full path to your own file, overriding the built-in choice. |
Rhythm is worth more than it sounds. Pitch is hard to place when you are not paying attention, but three quick pulses reads as urgent from across the room. The defaults use it: one pulse for a finished turn, two when something needs you, three tighter ones for a rate limit.
Volume has platform limits. On Windows anything below 100 plays through MediaPlayer rather than SoundPlayer, which has no volume control at all. On Linux aplay and canberra-gtk-play cannot set volume and play at the system level.
The config file is parsed, never sourced, so nothing in it can execute.
How well the focus check works varies by platform, and it is deliberately conservative: any uncertainty resolves to "not focused", so the failure mode is an alert you did not strictly need rather than a missed one.
- Windows compares the foreground window's process against this process's ancestry, so it answers "is my terminal in front", not merely "is a terminal in front".
- macOS compares the frontmost application against a list of known terminals and editors, so it is app-level rather than window-level.
- Linux needs
xdotoolunder X11. Wayland exposes no portable way to ask, so there it always alerts.
Everything in the table above is a config change. Only the sound files themselves need editing ~/.claude/claude-notify.ps1 or ~/.claude/claude-notify.sh, and re-running the installer overwrites those, so keep a copy of your changes. Your config file is safe either way.
Nine sounds are installed to ~/.claude/claude-sounds/default/, and they are what you hear by default on every platform. That is deliberate: relying on system sounds meant the alerts differed per machine, and on a minimal Linux install there were often none at all.
The quickest way to use your own is <KIND>_SOUND in the config, which takes a full path and needs no editing of any script.
If the bundled sounds are missing, the notifier falls back to the system set: C:\Windows\Media on Windows, ~/Library/Sounds and /System/Library/Sounds on macOS, and the freedesktop theme directories on Linux.
The sounds are generated rather than sourced, by build/make-sounds.py, so they are reproducible and carry no licensing question. Six of the nine are interchangeable finish chimes, which is what gives PROJECT_PITCH its range.
Claude Code hands its status line the real figures, straight from Anthropic:
"rate_limits": {
"five_hour": { "used_percentage": 23.5, "resets_at": 1738425600 },
"seven_day": { "used_percentage": 41.2, "resets_at": 1738857600 }
}The installer registers a small status line script that saves those times, and the notifier alerts when they pass. Three things follow from using the server's own numbers rather than a local guess:
- It costs nothing. Claude Code draws that bar anyway. No request is made and no tokens are used.
- It counts every surface. The web, the phone, the desktop app, another machine. All of it is already in that percentage.
- You never have to hit the limit. The reset time is there at 5% just as much as at 100%.
Two alerts come out of it: LIMIT_RESET when the five hour window rolls over,
and WEEKLY_RESET for the seven day one.
The reset time has to be learned from somewhere. Close Claude Code and the alert still fires, because the time is already on disk. But if nothing has read it since the current window began, nothing on your machine knows when the window ends.
Earshot for Web closes most of that. It reads the same figures from the usage endpoint claude.ai already calls for itself, every ten minutes, only while a claude.ai tab is open. It runs no model, so it costs nothing either.
Because the window is per account rather than per surface, only one of them has to read it. Spend the whole window in the desktop app and the countdown is still right, so long as Claude Code or a claude.ai tab was open at some point.
Different accounts are kept apart rather than merged. Each surface writes its own file tagged with the account it saw, and an alert for one account never stands in for another. Claude Code signed into one account and the browser into another gives two separate countdowns, which is the only honest answer.
The gap that remains, stated plainly: a window in which you open neither Claude Code nor claude.ai in a browser has nothing to learn the time from. The Claude desktop app does not record it anywhere durable, and the one route that would work means decrypting its session cookie, which is not something worth doing for a countdown.
If you already have a status line, the installer leaves it alone and prints the one line to add to your own.
Neither alert can be handled by a hook alone, because a hook exits the moment it finishes and cannot wait five hours. So the notifier starts a copy of itself in the background to watch the clock. No daemon to install, and it works whether or not the desktop app is present.
The watcher compares against an absolute time rather than sleeping for a duration. Close the lid for three hours, open it, and the next check sees the time has passed and fires immediately.
If you hit the limit before the status line has ever run, there is nothing to
read, so the alert falls back to counting WINDOW_HOURS forward and says so:
"about five hours have passed, your limit has probably reset". A real reset time
always wins over that guess.
Set NTFY_TOPIC to a long, random string, install the free
ntfy app, and subscribe to that topic. That is the whole
setup: no account, no API key, no server.
The topic name is the only secret. Anyone who knows it can read your notifications and send to them, so make it long and random, and do not put anything sensitive in an alert. It is also a third-party server, so the message leaves your machine. Both are why this is off by default.
Pushes are fired without waiting: a phone that is unreachable never costs you the local alert that already played.
A pack is just a folder. To make your own, put .wav files in ~/.claude/claude-sounds/<name>/ using the same filenames as default/, then set SOUND_PACK=<name>.
You do not need a complete set. Anything the pack is missing falls back to default/, so a pack containing one file changes exactly one sound.
The filenames are chime-glass, chime-soft, chime-bright, chime-low, chime-warm and chime-mid for finished turns, then alert-attention, alert-limit and alert-error.
On Windows it installs as claude-sounds.ps1 beside the notifier, so:
& "$env:USERPROFILE\.claude\claude-sounds.ps1" limits
& "$env:USERPROFILE\.claude\claude-sounds.ps1" mute 1hSame commands and same output as the Unix version, so a habit learned on one machine carries to the other.
Installed alongside the notifier on Linux and macOS, at
~/.claude/claude-sounds-cli.
claude-sounds-cli status is any of this actually working
claude-sounds-cli stats how your turns have been going
claude-sounds-cli log recent alerts, and why some were skipped
claude-sounds-cli test blocked
claude-sounds-cli mute 1h 30m, 2h, or off
status is the one worth knowing. It answers "why is nothing happening" in one
go: whether the hooks are registered, whether the notifier and sounds are there,
whether any audio player exists, whether something is currently muting you, and
when the last alert fired.
It reads and writes the same files the notifier uses, with no state of its own, so it cannot disagree with what actually happens.
Nothing fires at all. Restart Claude Code first, since settings are read at startup. Then run claude --debug and watch for the hook lines as events happen.
Hooks run but there is no sound. First find out what the notifier actually decided. It will tell you:
CLAUDE_NOTIFY_DEBUG=1 ~/.claude/claude-notify.sh blocked$env:CLAUDE_NOTIFY_DEBUG=1; & "$env:USERPROFILE\.claude\claude-notify.ps1" -Kind blockedkind=blocked sound=/usr/share/sounds/freedesktop/stereo/dialog-warning.oga player=paplay notified=notify-send detail=...
sound=none means no sound file was found. player=bell on Unix, or player=beep on Windows, means a file was found but playback failed, so it fell back to a raw tone. That distinction tells you which of the two fixes below you need.
A suppressed= field instead means it worked exactly as configured and chose to stay quiet: too-quick (under MIN_SECONDS), focused, debounced, quiet-hours, or muted.
To see what it would do without actually hearing it, add CLAUDE_NOTIFY_DRYRUN=1. It resolves the sound, volume and pattern and reports them, but plays nothing and raises no notification.
On a minimal Linux install, the sound theme is often missing:
sudo apt install sound-theme-freedesktop libcanberra-gtk-moduleOn Linux you also need a player that handles .oga files. paplay, pw-play, and canberra-gtk-play all do; aplay only handles .wav.
No desktop notification on macOS. osascript routes notifications through Script Editor, which fails silently if it lacks notification permission. Run osascript -e 'display notification "test"' once, then enable Script Editor under System Settings, Notifications.
Wrong sound for the wrong event. Run /hooks and check the matcher strings against the matcher values in the docs. An invalid matcher does not error, it just never matches.
- Claude Code with hooks support
- Windows: PowerShell 5.1 or later, which ships with Windows 10 and 11
- Linux and macOS:
bash, pluspython3for the installer only. The alerts themselves need no Python. A desktop notification needsnotify-sendon Linux orosascripton macOS, but sound works without either.
A small configurator for everything below: toggle each alert, pick a sound and hear it, set volumes and rhythms, and see recent activity including the alerts it deliberately stayed quiet for. It can install and remove the hooks itself, so a machine that has never run the scripts needs no terminal.
It also sits in the tray, so the two things you reach for in a hurry, silencing the alerts and opening the settings, do not need the window open. A temporary mute is written as an expiry time rather than a flag, so one you forget about turns itself back on.
Downloads are per platform, on the releases page:
.msi for Windows, and .AppImage or .deb for Linux. A macOS build is not
published yet.
The builds are unsigned, because signing certificates cost money and this project does not spend any. Windows SmartScreen will warn until enough people have installed it: choose More info, then Run anyway. Linux is unaffected.
The scripts work on macOS today. Only the desktop app is Windows and Linux for now.
The app is optional and never required. The hooks own the alerting; the app only edits the same config file you could edit by hand. Removing the app leaves your alerts working.
It installs like any other program, so it is in the Start menu on Windows and the applications list on Linux, under Claude Code Sounds.
Closing the window does not quit it. It keeps running in the tray, which is the point: that is what mutes the alerts without opening anything. Click the tray icon to bring the window back, or right click it for mute and quit.
If it is not in the tray either, it is not running. Start it again from the Start menu, or directly:
# Windows. The .msi installs to Program Files under the full product name.
& "$env:ProgramFiles\Claude Code Sounds\Claude Code Sounds.exe"# Linux, if installed from the .deb
claude-code-sounds
# or, if you kept the AppImage, whatever you named the file
./claude-code-sounds_*.AppImageBoth test suites run the real installer against a throwaway home directory, so they never touch your own ~/.claude.
bash tests/test-unix.shpowershell -ExecutionPolicy Bypass -File tests\test-windows.ps1CI runs them on Linux, macOS and Windows, along with shellcheck and PSScriptAnalyzer. See CONTRIBUTING.md for what the tests protect and how to lint the generated notifier scripts.
The two installers are separate scripts rather than one cross-platform script, because the audio and notification paths have nothing in common between Windows and Unix.
There is a cleaner route I did not take. Hooks can return a terminalSequence field and let Claude Code emit the notification escape sequence itself, which hands the decision to your terminal emulator and would work identically everywhere. It is worth looking at if you want your existing terminal notification setup to handle this instead of a bespoke script.
The Linux and macOS script is exercised by CI on real Linux and macOS runners, covering the settings merge, idempotency, uninstall, sound-file selection and the player fallback chain. What CI cannot cover is audio itself: the runners have no audio device, so the moment a player actually opens a sink is still the least proven part of this. If a sound does not play on your setup, open an issue with your distribution and audio stack and I will add it to the fallback chain.
MIT, see LICENSE. Trademarks, privacy and the absence of any warranty are covered in NOTICE.