Claude Code sound when done: play a sound when it finishes or needs you

Updated 24 October 2026

A sound is the simplest way to stop checking the terminal every thirty seconds. Claude Code doesn’t play one by itself, but two hooks cover it: Stop when Claude finishes a reply, and PermissionRequest when it’s waiting for your approval. Here’s the setup for macOS, Linux, Windows and WSL, and what to check when it stays quiet.

The short answer

SystemCommand for your hook
macOSafplay /System/Library/Sounds/Glass.aiff
Linuxpaplay /usr/share/sounds/freedesktop/stereo/complete.oga
Windows and WSLpowershell.exe with Media.SoundPlayer and PlaySync(), below
Any terminal, including over SSHThe terminal bell, below

Put the command in a Stop hook for “finished” and a PermissionRequest hook for “needs you”, in ~/.claude/settings.json so it works in every project.

1. Which event to hook: when done, or when input is needed

You want a sound when…EventTiming
Claude finished replyingStopImmediately, after every reply
Claude needs permissionPermissionRequestImmediately, when the prompt appears
Claude asks you a multiple-choice questionPreToolUse on AskUserQuestionImmediately
You walked away and Claude is waitingNotification with idle_promptAbout 60 seconds after it finishes, if you haven’t typed
The turn died on an API errorStopFailureImmediately, instead of Stop

Two things to know before you start. Stop runs whenever Claude finishes responding, including after a one-line answer, and it doesn’t run when you interrupt with Esc. And the permission_prompt notification many guides suggest only fires after about six seconds without typing, so it’s unreliable while you’re at the keyboard. PermissionRequest has no delay.

2. macOS

afplay ships with macOS and plays any audio file. Try a sound in your terminal first:

afplay /System/Library/Sounds/Glass.aiff

The built-in sounds in /System/Library/Sounds/ are Basso, Blow, Bottle, Frog, Funk, Glass, Hero, Morse, Ping, Pop, Purr, Sosumi, Submarine and Tink, each an .aiff file. Then add both hooks:

~/.claude/settings.json{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "afplay /System/Library/Sounds/Glass.aiff",
            "async": true
          }
        ]
      }
    ],
    "PermissionRequest": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "afplay /System/Library/Sounds/Funk.aiff",
            "async": true
          }
        ]
      }
    ]
  }
}

"async": true runs the sound in the background. Without it, Claude Code waits for the sound to finish, so the permission prompt appears a second later than it should. Add -v 0.5 after afplay to play it at half volume. If you already have a "hooks" key, add Stop and PermissionRequest inside it.

A PermissionRequest hook that prints nothing doesn’t approve or deny anything. The prompt still waits for you.

3. Linux

Most desktops run PipeWire or PulseAudio, and paplay works with both. The sounds come from the freedesktop sound theme:

sudo apt-get install pulseaudio-utils sound-theme-freedesktop
paplay /usr/share/sounds/freedesktop/stereo/complete.oga
~/.claude/settings.json{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "paplay /usr/share/sounds/freedesktop/stereo/complete.oga",
            "async": true
          }
        ]
      }
    ],
    "PermissionRequest": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "paplay /usr/share/sounds/freedesktop/stereo/message-new-instant.oga",
            "async": true
          }
        ]
      }
    ]
  }
}

Other options, if you prefer:

Other files in the same folder include bell.oga, dialog-warning.oga and dialog-information.oga. Over SSH, the sound plays on the remote machine, which usually has no speakers. Use the terminal bell there.

4. Windows

Windows has no one-word sound command, but PowerShell can play any .wav file through .NET’s SoundPlayer. Test it in a terminal:

powershell.exe -NoProfile -Command "(New-Object Media.SoundPlayer 'C:\Windows\Media\tada.wav').PlaySync()"

Use PlaySync(), not Play(). Play() returns straight away, PowerShell exits, and the sound is cut off before it starts. That’s the most common reason a Windows sound hook stays silent.

In the hook, give the command as an executable plus args. Claude Code then starts powershell.exe directly, whether or not Git Bash is installed, and there’s no shell quoting to get wrong:

%USERPROFILE%\.claude\settings.json{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "powershell.exe",
            "args": ["-NoProfile", "-Command", "(New-Object Media.SoundPlayer 'C:\\Windows\\Media\\tada.wav').PlaySync()"],
            "async": true
          }
        ]
      }
    ],
    "PermissionRequest": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "powershell.exe",
            "args": ["-NoProfile", "-Command", "(New-Object Media.SoundPlayer 'C:\\Windows\\Media\\notify.wav').PlaySync()"],
            "async": true
          }
        ]
      }
    ]
  }
}

Backslashes are doubled because it’s JSON. Run dir C:\Windows\Media to see which sounds your Windows version has. SoundPlayer only plays .wav, so convert anything else first.

5. WSL

Claude Code inside WSL runs as a Linux program, but it can still start Windows programs. The Windows config above works unchanged in ~/.claude/settings.json inside your distro, because WSL finds powershell.exe on your Windows PATH. Check that first:

powershell.exe -NoProfile -Command "(New-Object Media.SoundPlayer 'C:\Windows\Media\tada.wav').PlaySync()"

If you get command not found, Windows interop or the Windows path is turned off in /etc/wsl.conf. Look for appendWindowsPath = false under [interop], remove it, and run wsl --shutdown from Windows. Or use the full path, /mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe, as the hook’s command. Keep the sound file path in Windows form (C:\…), since PowerShell is the one opening it.

6. A different sound for each event

Distinct sounds tell you what happened without looking: done, needs approval, has a question, or broke. This macOS config uses four. Swap the commands for the Linux or Windows ones above:

~/.claude/settings.json{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          { "type": "command", "command": "afplay /System/Library/Sounds/Glass.aiff", "async": true }
        ]
      }
    ],
    "PermissionRequest": [
      {
        "matcher": "",
        "hooks": [
          { "type": "command", "command": "afplay /System/Library/Sounds/Funk.aiff", "async": true }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "AskUserQuestion",
        "hooks": [
          { "type": "command", "command": "afplay /System/Library/Sounds/Ping.aiff", "async": true }
        ]
      }
    ],
    "StopFailure": [
      {
        "hooks": [
          { "type": "command", "command": "afplay /System/Library/Sounds/Basso.aiff", "async": true }
        ]
      }
    ]
  }
}

Leave Notification out of this, or limit it to idle_prompt. A Notification hook with no matcher also fires for permission_prompt, so you’d hear a second sound six seconds after the first.

To play the “done” sound only when nothing is still running in the background, such as a background shell or subagent, check the background_tasks field that Stop receives:

~/.claude/settings.json{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "if jq -e '(.background_tasks // []) | length == 0' >/dev/null; then afplay /System/Library/Sounds/Glass.aiff; fi",
            "async": true
          }
        ]
      }
    ]
  }
}

That needs jq (brew install jq or sudo apt-get install jq). The if keeps the hook’s exit code at 0 when it skips the sound, so you don’t get a hook error notice.

7. A beep when done: the terminal bell

The bell needs no sound files and works over SSH, because it’s just a character sent to your own terminal. For notifications, one setting turns it on:

~/.claude/settings.json{
  "preferredNotifChannel": "terminal_bell"
}

That rings on Claude Code’s notifications, which follow the same timing as above: about six seconds after a permission prompt if you’re not typing, and about 60 seconds after it finishes. For a bell the moment Claude finishes, a hook can’t just print \a: hooks have no terminal, and writing to /dev/tty fails. Return the bell in terminalSequence and Claude Code writes it for you:

~/.claude/settings.json{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "printf '%s\\n' '{\"terminalSequence\":\"\\u0007\"}'"
          }
        ]
      }
    ]
  }
}

Leave out async here: an async hook’s output only reaches Claude Code on the next turn. Whether a bell makes a noise, flashes or bounces the Dock icon is up to your terminal. In Apple’s Terminal, Claude Code’s first-run setup (/terminal-setup) turns the audible bell off; turn it back on under Settings → Profiles → Advanced → Audible bell.

8. Spoken alerts

With several sessions open, a voice that says which project finished beats a chime. Claude Code sets CLAUDE_PROJECT_DIR for every hook, so the folder name is easy to get:

~/.claude/settings.json (macOS){
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "say \"$(basename \"$CLAUDE_PROJECT_DIR\") is done\"",
            "async": true
          }
        ]
      }
    ],
    "PermissionRequest": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "say \"$(basename \"$CLAUDE_PROJECT_DIR\") needs permission\"",
            "async": true
          }
        ]
      }
    ]
  }
}

On Linux, replace say with spd-say. On Windows, use powershell.exe with these args:

["-NoProfile", "-Command", "Add-Type -AssemblyName System.Speech; (New-Object System.Speech.Synthesis.SpeechSynthesizer).Speak('Claude is done')"]

9. No sounds from subagents or scripts

10. Troubleshooting

For anything else, start Claude Code with claude --debug-file /tmp/claude.log and look for your hook in the log. Claude Code hooks: a practical guide covers debugging in detail.

More than a sound: answer from your browser

A sound tells you to go back to the terminal. Agent Pets brings the prompt to you instead. Each Claude Code, Codex, OpenCode and omp session becomes a pet walking along the bottom of every page in Chrome. When one needs permission, its pet bounces; click it and answer Allow, Always or Deny. A capsule over each pet says Thinking, Your Turn or Done, and in another app a desktop notification names the pet that needs you, with Allow and Deny on it.

npx getagentpets init adds the hooks for you, next to any sound hooks you already have. It runs entirely on your computer.

Get Agent Pets — was $25, now $6Get Agent Pets — $25

Questions

How do I make Claude Code play a sound when it finishes?

Add a Stop hook to ~/.claude/settings.json that runs a sound command: afplay /System/Library/Sounds/Glass.aiff on macOS, or paplay /usr/share/sounds/freedesktop/stereo/complete.oga on Linux. Type /hooks in a session to check it loaded.

How do I get a sound when Claude Code needs permission?

Use a PermissionRequest hook. It runs as soon as Claude asks. A Notification hook with the permission_prompt matcher waits until you haven’t typed for about six seconds.

Why does the sound play after every reply?

Stop runs every time Claude finishes responding, not only when a whole task is done. If you only want a sound once you’ve walked away, use a Notification hook with the idle_prompt matcher instead. It fires about 60 seconds after Claude finishes, and only if you haven’t typed since.

How do I play a sound from Claude Code on Windows or WSL?

Run powershell.exe with System.Media.SoundPlayer and call PlaySync() on a .wav file from C:\Windows\Media. Play() returns at once and PowerShell exits before the sound starts. From WSL, the same command works through Windows interop.

How do I make Claude Code beep when done?

Have a Stop hook return the bell character in terminalSequence, as in the terminal bell section above. Claude Code writes it to your terminal the moment Claude finishes, even over SSH. Whether the bell beeps or flashes depends on your terminal’s settings.

Can I change the Claude Code notification sound?

Claude Code doesn’t play a sound of its own, so there’s no built-in setting for it. With a hook, the sound is whatever file you pass to afplay, paplay or SoundPlayer, so use a different file for each event if you like.

Sources: Claude Code’s documentation on the hooks reference, automating actions with hooks and terminal configuration; Microsoft’s SoundPlayer.PlaySync reference and WSL docs on running Windows tools from Linux and wsl.conf; the sound-theme-freedesktop file list and the canberra-gtk-play man page.

More guides