anotifier / guides / claude-code-notification-sound
[ GUIDE · CLAUDE CODE SOUNDS ]
Claude Code logo

Claude Code notification sound: play one when it finishes, change it, or turn it off

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:

$ npx anotifier@latest setup

Updated 2026-10-16 · also see the Claude Code notifier and the full docs

Option 1: the terminal bell

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" }
  • The sound is your terminal's bell, so it is set, and can be muted, in the terminal's own settings.
  • Apple Terminal: Claude Code's first-run terminal setup turns the audible bell off in your profile. Turn it back on under Settings > Profiles > Advanced > "Audible bell".
  • VS Code's integrated terminal plays the bell sound only when its 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.
  • The bell travels over SSH to the terminal you are sitting at.

Option 2: a hook with one sound for "done", another for "needs you"

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"
          }
        ]
      }
    ]
  }
}
PlatformCommandNotes
macOSafplay /System/Library/Sounds/Glass.aiffThe 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
Linuxpaplay /usr/share/sounds/freedesktop/stereo/complete.ogaNeeds 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 PowerShellAny .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()\""
          }
        ]
      }
    ]
  }
}

Pick a sound per event

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:

EventFires whenA macOS sound to try
StopClaude finishes responding. It fires after every response, not only at the end of a big task, and not when you interruptGlass
PermissionRequestClaude is about to ask you to approve a tool, with no wait. It does not run for a sandboxed command's network requestSosumi
Notification with matcher permission_promptAn approval has waited about six seconds. Each keystroke in the terminal pushes the timer backPing
Notification with matcher idle_promptClaude 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 resetTink
Notification with matcher elicitation_dialogAn MCP server opens a form and you haven't typed for about six secondsPing
SubagentStopA subagent finishes. A turn with several subagents plays it several times, so use a very quiet sound or nonePop
StopFailureA turn ends because of an API errorBasso

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
          }
        ]
      }
    ]
  }
}

Only some notifications

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.

Native Windows and WSL

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.
  • In Windows Terminal, 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.
  • Windows and WSL toast notifications covers the toast side, which takes a different road on each.

Linux: paplay, pw-play, aplay or canberra-gtk-play

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:

PlayerCommandNotes
paplaypaplay /usr/share/sounds/freedesktop/stereo/complete.ogaPlays through PulseAudio, or PipeWire's PulseAudio server. On Debian and Ubuntu it is in pulseaudio-utils
pw-playpw-play /usr/share/sounds/freedesktop/stereo/complete.ogaPipeWire's own player. It reads any format libsndfile supports. --volume=0.5 sets the stream volume (the default is 1.0)
aplayaplay -q /path/to/sound.wavALSA, 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-playcanberra-gtk-play -i completePlays 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.

Don't block Claude: run the sound in the background

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.
  • Once an async hook is running in the background, Claude Code doesn't enforce its timeout.
  • Without 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.
  • Because a hook blocks until it finishes, a PermissionRequest sound without async can delay the prompt, so make that one async.

No sound? Troubleshooting

Work down this list from the top.

  • Your terminal gets no desktop notification. The default "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.
  • Apple Terminal's audible bell is off. If you accepted Claude Code's first-run terminal setup, it turned that off in your profile. With the default 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.
  • The VS Code terminal bell is silent. VS Code plays the bell sound only if 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.
  • Volume and output. A hook's player uses your current output device and volume, so check both, and that nothing is muted.
  • The hook never ran. In an interactive session, Claude Code holds back hooks from every settings file, including your own ~/.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.
  • It isn't time yet. 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.
  • A player is missing. command not found for paplay or aplay means the package from the Linux table isn't installed. aplay can't play .oga or .mp3.
  • Windows paths in JSON. settings.json is JSON, so every backslash is written twice: C:\\Windows\\Media\\chimes.wav. A single backslash is a broken escape.
  • You are on SSH. The terminal bell reaches your local terminal, but a hook plays on the remote machine, which may have no speakers.
  • tmux. The built-in desktop notification needs set -g allow-passthrough on in ~/.tmux.conf to reach the outer terminal.

Turn the sound off

Which switch you need depends on where the sound comes from:

Sound sourceHow to turn it off
Claude Code's built-in alertSet "preferredNotifChannel": "notifications_disabled" in ~/.claude/settings.json. The other values are auto, terminal_bell, iterm2, iterm2_with_bell, kitty and ghostty
A hook you wroteDelete 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 bellApple Terminal: turn off Audible bell in the profile. Windows Terminal: "bellStyle": "none". VS Code: "accessibility.signals.terminalBell": { "sound": "off" }
anotifierSnooze, 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.
  • Quiet hours silence every channel inside a daily window. They are off by default: set quietHours.enabled to true, and from and to as "HH:MM", such as 22:00 to 08:00.
  • The toast and its sound: 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.

Sounds with anotifier

npx anotifier@latest setup

anotifier 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:

EventDefaultWindows playsmacOS plays
Task finishedIMIMGlass
Approval or questionReminderReminderPing
  • Windows takes the notification sound names Default, IM, Mail, Reminder, SMS, and Alarm.
  • macOS maps those names to built-in sounds (Mail to Purr, SMS to Tink, Alarm to Sosumi), and also accepts any of the 14 built-in names directly.
  • Linux ignores sound names; urgent events are sent at critical urgency instead.
  • 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 } } }

Claude Code sounds: common questions

How do I make Claude Code play a sound when it's done?

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.

Why doesn't Claude Code make a sound in Apple Terminal?

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.

Can I use my own sound file?

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.

Does the sound work over SSH?

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.

How do I change the Claude Code notification sound?

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.

Why is there no sound from Claude Code in the VS Code terminal?

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.

How do I turn off the Claude Code sound?

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.

Can Claude Code play a different sound when it needs permission?

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.

Try it

One command wires every agent you have installed: Claude Code, Codex CLI, Cursor, and Gemini CLI.

npx anotifier@latest setup

Other guides