Two ways, with nothing to install: set "preferredNotifChannel": "terminal_bell" in ~/.claude/settings.json to ring the terminal bell, or add a Stop hook that plays a sound file, such as afplay /System/Library/Sounds/Glass.aiff on macOS. Give the Notification hook a different sound and you can tell "done" from "needs you" by ear. This is the sound half of the Claude Code notifications guide, which covers every alert channel. Below: which events deserve a sound, Windows and WSL, Linux players, keeping a sound from blocking Claude, fixes when you hear nothing, and how to turn the sound off.
Rather skip the manual setup? One command wires every agent you have installed to desktop toasts, phone push, and Slack, Discord or Telegram:
Updated 2026-10-16 · also see the Claude Code notifier and the full docs
By default Claude Code sends a desktop notification only in Ghostty, Kitty, and iTerm2. In any other terminal, this setting makes it ring the terminal bell instead when it finishes or waits for you:
{ "preferredNotifChannel": "terminal_bell" }accessibility.signals.terminalBell setting allows it; the fix is below. A hook plays no matter which terminal you use, and anotifier in VS Code covers the ready-made one.Hooks run alongside Claude Code's built-in notification. Stop fires when Claude finishes a turn; Notification fires when it needs you. This macOS example plays Glass when Claude is done and Ping when it is waiting:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "afplay /System/Library/Sounds/Glass.aiff"
}
]
}
],
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "afplay /System/Library/Sounds/Ping.aiff"
}
]
}
]
}
}| Platform | Command | Notes |
|---|---|---|
| macOS | afplay /System/Library/Sounds/Glass.aiff | The 14 built-in sounds: Basso, Blow, Bottle, Frog, Funk, Glass, Hero, Morse, Ping, Pop, Purr, Sosumi, Submarine, Tink. afplay also plays any .aiff, .mp3, or .wav of your own |
| Linux | paplay /usr/share/sounds/freedesktop/stereo/complete.oga | Needs PulseAudio or PipeWire and the freedesktop sound theme. message-new-instant.oga makes a good needs-you sound |
| Windows | (New-Object Media.SoundPlayer 'C:\Windows\Media\chimes.wav').PlaySync() in PowerShell | Any .wav in C:\Windows\Media, such as tada.wav or notify.wav. PlaySync waits for the sound to end so PowerShell doesn't exit first |
On Windows, the hook calls PowerShell. In settings.json the backslashes are escaped:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "powershell.exe -NoProfile -Command \"(New-Object Media.SoundPlayer 'C:\\Windows\\Media\\chimes.wav').PlaySync()\""
}
]
}
],
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "powershell.exe -NoProfile -Command \"(New-Object Media.SoundPlayer 'C:\\Windows\\Media\\notify.wav').PlaySync()\""
}
]
}
]
}
}Claude Code has more hook events than Stop and Notification. These are the ones worth a sound. Each event is its own entry in settings.json, so each can play its own file:
| Event | Fires when | A macOS sound to try |
|---|---|---|
Stop | Claude finishes responding. It fires after every response, not only at the end of a big task, and not when you interrupt | Glass |
PermissionRequest | Claude is about to ask you to approve a tool, with no wait. It does not run for a sandboxed command's network request | Sosumi |
Notification with matcher permission_prompt | An approval has waited about six seconds. Each keystroke in the terminal pushes the timer back | Ping |
Notification with matcher idle_prompt | Claude finished about 60 seconds ago, you haven't typed since, and no background agent is still running. It isn't sent while Claude waits for a usage limit to reset | Tink |
Notification with matcher elicitation_dialog | An MCP server opens a form and you haven't typed for about six seconds | Ping |
SubagentStop | A subagent finishes. A turn with several subagents plays it several times, so use a very quiet sound or none | Pop |
StopFailure | A turn ends because of an API error | Basso |
To hear the moment Claude asks for permission, use PermissionRequest: the permission_prompt notification only arrives after the six-second wait. This setup plays Glass for done, Sosumi for an approval, and Tink for the idle reminder:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "afplay /System/Library/Sounds/Glass.aiff",
"async": true
}
]
}
],
"PermissionRequest": [
{
"hooks": [
{
"type": "command",
"command": "afplay /System/Library/Sounds/Sosumi.aiff",
"async": true
}
]
}
],
"Notification": [
{
"matcher": "idle_prompt",
"hooks": [
{
"type": "command",
"command": "afplay /System/Library/Sounds/Tink.aiff",
"async": true
}
]
}
]
}
}A hook without a matcher plays for every notification type, including ones that don't need you. To sound only for approvals, add "matcher": "permission_prompt" to the entry. The permission notifications guide lists every type and when it fires.
On native Windows, Claude Code runs a hook command with Git Bash, or with PowerShell when Git Bash isn't installed. The powershell.exe -Command form above works under either. To skip the nested powershell.exe and the quote escaping, set "shell": "powershell" on the hook and write the PowerShell directly:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"shell": "powershell",
"command": "(New-Object Media.SoundPlayer 'C:\\Windows\\Media\\chimes.wav').PlaySync()",
"async": true
}
]
}
]
}
}Inside WSL, Claude Code runs the hook in Linux, so afplay isn't there and paplay needs a Linux audio setup. Call Windows PowerShell through Windows interop instead, with a Windows path, so the sound plays on the Windows side. Claude Code's hooks guide notes that powershell.exe must be on your PATH through interop. The same command works as it is:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "powershell.exe -NoProfile -Command \"(New-Object Media.SoundPlayer 'C:\\Windows\\Media\\tada.wav').PlaySync()\"",
"async": true
}
]
}
]
}
}SoundPlayer plays .wav files only. Those in C:\Windows\Media are fine; an .mp3 needs a different player.preferredNotifChannel set to terminal_bell makes the bell do whatever its bellStyle says: audible is the default and none is silent. With all, the sound plays only while the window has focus; otherwise only the taskbar icon flashes. bellSound picks the file.Linux has no single player, so use the one your desktop has. The freedesktop sound theme in /usr/share/sounds/freedesktop/stereo/ gives you ready-made files such as complete.oga, message-new-instant.oga, bell.oga and dialog-warning.oga:
| Player | Command | Notes |
|---|---|---|
paplay | paplay /usr/share/sounds/freedesktop/stereo/complete.oga | Plays through PulseAudio, or PipeWire's PulseAudio server. On Debian and Ubuntu it is in pulseaudio-utils |
pw-play | pw-play /usr/share/sounds/freedesktop/stereo/complete.oga | PipeWire's own player. It reads any format libsndfile supports. --volume=0.5 sets the stream volume (the default is 1.0) |
aplay | aplay -q /path/to/sound.wav | ALSA, in alsa-utils. It plays WAV, VOC, AU and raw files, so it can't play the .oga theme files. -q keeps it quiet on the console |
canberra-gtk-play | canberra-gtk-play -i complete | Plays a sound by its sound-theme name instead of a path; -f PATH plays a file. On Debian it is in gnome-session-canberra |
The theme files come from the sound-theme-freedesktop package on Debian and Ubuntu. Run ls /usr/share/sounds/freedesktop/stereo/ to see what you have, and try a command in a terminal before you put it in settings.json.
By default, a hook blocks Claude until it finishes. A sound has nothing to report back, so add "async": true to a command hook and Claude carries on while the sound plays:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "afplay /System/Library/Sounds/Glass.aiff",
"async": true
}
]
}
]
}
}async works on "type": "command" hooks only. An async hook can't block or steer Claude, which is fine for a sound.timeout.async, set a timeout in seconds so a stuck audio device can't hold Claude up: the default for a command hook is 600 seconds. "timeout": 5 is plenty for a chime.PermissionRequest sound without async can delay the prompt, so make that one async.Work down this list from the top.
"preferredNotifChannel": "auto" sends one in iTerm2, Ghostty and Kitty, rings the bell in Terminal.app only when its audible bell is off, and does nothing elsewhere, so set terminal_bell or use a hook.auto, Claude Code then rings the bell in Terminal.app only because the audible bell is off, and you hear nothing. For a sound, set "preferredNotifChannel": "terminal_bell" and turn the bell back on under Settings > Profiles > Advanced > "Audible bell", or use a hook.accessibility.signals.terminalBell allows it, and its default (auto) means only when a screen reader is attached. Set "accessibility.signals.terminalBell": { "sound": "on" } in VS Code's settings, or use a hook, which doesn't depend on the terminal.~/.claude/settings.json, until you accept the workspace trust dialog for the folder. Then type /hooks and check the hook is listed under the right event; the menu is read-only. Run the command in your own shell first. For details, start Claude Code with claude --debug-file claude-debug.log (any path works) and read the file. Plain claude --debug writes to ~/.claude/debug/<session-id>.txt and prints nothing to the terminal.permission_prompt waits about six seconds and idle_prompt about 60. The built-in alert is aimed at moments when you appear to be away from the terminal, so it may stay quiet while you are typing in the window. Stop fires after every response, but not after an interrupt.command not found for paplay or aplay means the package from the Linux table isn't installed. aplay can't play .oga or .mp3.settings.json is JSON, so every backslash is written twice: C:\\Windows\\Media\\chimes.wav. A single backslash is a broken escape.set -g allow-passthrough on in ~/.tmux.conf to reach the outer terminal.Which switch you need depends on where the sound comes from:
| Sound source | How to turn it off |
|---|---|
| Claude Code's built-in alert | Set "preferredNotifChannel": "notifications_disabled" in ~/.claude/settings.json. The other values are auto, terminal_bell, iterm2, iterm2_with_bell, kitty and ghostty |
| A hook you wrote | Delete its entry from settings.json. To pause your own hooks, set "disableAllHooks": true, which also turns off a custom status line. Managed hooks keep running unless it is set in managed settings. There is no way to switch off one hook and keep it configured |
| The terminal's bell | Apple Terminal: turn off Audible bell in the profile. Windows Terminal: "bellStyle": "none". VS Code: "accessibility.signals.terminalBell": { "sound": "off" } |
| anotifier | Snooze, quiet hours, or turn the toast off; the bell setting alone doesn't silence the toast's sound. See the list below |
With anotifier, from the least to the most permanent:
npx anotifier@latest snooze 2h silences every channel for a while, the toast and its sound and the terminal bell included. snooze off cancels it, and snooze alone shows the current state.quietHours.enabled to true, and from and to as "HH:MM", such as 22:00 to 08:00.toast.enabled: false in ~/.anotifier/config.json for every event, or toastEnabled: false under one event in events. The terminal bell is a separate switch (terminalBell.enabled: false, or terminalBellEnabled: false for one event, as in Sounds with anotifier), and turning it off leaves the toast's sound on.npx anotifier@latest uninstall asks (y/N) first, backs up the files it edits to ~/.anotifier/backups/, and removes anotifier's hooks from the tools it wired. It keeps ~/.anotifier/, and it doesn't touch Claude Code's preferredNotifChannel or hooks you wrote yourself, so a sound from either keeps playing.npx anotifier@latest setupanotifier plays a sound with each desktop toast and rings the terminal bell, with different defaults for finished and needs-you events. Set the sound per event with toastSound in ~/.anotifier/config.json. This example uses macOS names:
| Event | Default | Windows plays | macOS plays |
|---|---|---|---|
| Task finished | IM | IM | Glass |
| Approval or question | Reminder | Reminder | Ping |
Default, IM, Mail, Reminder, SMS, and Alarm.Mail to Purr, SMS to Tink, Alarm to Sosumi), and also accepts any of the 14 built-in names directly.anotifier config sounds changes them interactively.{
"events": {
"task_complete": {
"toastSound": "Hero"
},
"needs_input": {
"toastSound": "Sosumi"
}
}
}The terminal bell is on by default, and it is separate from the toast's sound. With Claude Code 2.1.141 and newer, it rings through Claude Code's own terminal output, which also works inside tmux and GNU screen. Turn it off for one event with terminalBellEnabled, or for all of them with terminalBell.enabled:
{ "events": { "task_complete": { "terminalBellEnabled": false } } }Set "preferredNotifChannel": "terminal_bell" in ~/.claude/settings.json for the terminal bell, or add a Stop hook that plays a file: afplay /System/Library/Sounds/Glass.aiff on macOS, paplay with a freedesktop sound on Linux, or a PowerShell Media.SoundPlayer on Windows.
Claude Code's first-run terminal setup turns off the audible bell in your Apple Terminal profile. Turn it back on under Settings > Profiles > Advanced > Audible bell, or use a Stop hook with afplay.
Yes, with a hook: afplay and paplay play any file you point them at, and Media.SoundPlayer plays any .wav. anotifier's toastSound only takes the system sound names listed above.
The terminal bell and Claude Code's built-in desktop notification reach your local terminal over SSH. A hook runs on the remote machine, so a sound it plays comes out of the remote machine's speakers.
The preferredNotifChannel setting only picks the channel (bell or desktop notification), not the sound. To choose the sound, add a Stop or Notification hook that plays a file you pick: afplay with a .aiff, .mp3 or .wav on macOS, paplay, pw-play or aplay on Linux, or a PowerShell Media.SoundPlayer .wav on Windows. With anotifier, set toastSound per event in ~/.anotifier/config.json.
VS Code's integrated terminal plays the bell sound only if its accessibility.signals.terminalBell setting allows it, and the default, auto, means only when a screen reader is attached. Set "accessibility.signals.terminalBell": { "sound": "on" } in VS Code, or add a Stop hook that plays a file, which does not depend on the terminal.
Set "preferredNotifChannel": "notifications_disabled" in ~/.claude/settings.json to silence the built-in alert. Hooks still run, so also delete any sound hook, or set "disableAllHooks": true to pause your own hooks (managed hooks keep running unless it is set in managed settings). anotifier's toast has its own sound, so turning its terminal bell off is not enough: run npx anotifier@latest snooze 2h, or set toast.enabled to false in ~/.anotifier/config.json.
Yes. Add a PermissionRequest hook to play it the instant Claude asks, or a Notification hook with "matcher": "permission_prompt", which fires after the prompt has waited about six seconds. Give the Stop hook another sound for done.
One command wires every agent you have installed: Claude Code, Codex CLI, Cursor, and Gemini CLI.
npx anotifier@latest setup