Plays a sound when a long turn finishes.

A Claude Code mod that plays a sound when a long turn finishes, so you can look away while Claude works.
It plays after a turn only when all of these hold:
The clip is a short chime (sounds/done.wav, from the Cozy SFX pack).
| System | Player | Status |
|---|---|---|
| macOS | the engine's own (afplay), at the configured gain (default 3) | tested |
| Linux | the first that works of paplay, aplay, ffplay | not tested on a real machine |
| Windows | powershell.exe with Media.SoundPlayer | not tested on a real machine |
On macOS nothing else is needed. On Linux, install one of the three players if none is there (paplay comes with PulseAudio and PipeWire, aplay with ALSA, ffplay with ffmpeg). Windows needs nothing extra.
The mod finds the system by running uname, so it needs the terminal (CLI) version of Claude Code, where mods can run commands. If no player works, it stays silent and does not fail. The Linux and Windows players use the system volume, so the clip is quieter there than on macOS, where gain 3 boosts it. This applies to terminal sessions only; the desktop app and VS Code are not covered.
At the prompt of a Claude Code terminal session:
/plugin install done-sound --marketplace pongsixtyfour/mods
Answer y to add the marketplace, then pick a scope.
From a shell:
claude plugin install done-sound --marketplace pongsixtyfour/mods
In the desktop app, where /plugin does not run: click + next to the prompt box, then Plugins, Add marketplace with pongsixtyfour/mods, then Add plugin.
Volume is a plugin option. Change gain (0 to 4, default 3, macOS only) in /config, or under pluginConfigs in settings.json.
To change the shortest turn that earns a sound, edit MIN_MS at the top of hooks/register.ts (milliseconds, default 15_000).
To change the sound, replace sounds/done.wav with a .wav file (the Windows player only plays .wav) (keep the name, or update the path in register.ts).
claude plugin validate .
claude plugin test .
The tests cover when it plays (a long turn plays; a short, interrupted or subagent turn stays silent) and which player each system uses, with the commands faked.
hooks/register.ts 64 lines1import type { Register } from 'claude-code'
2
3const MIN_MS = 15_000
4const DEFAULT_GAIN = 3
5const CLIP = 'sounds/done.wav'
6
7// Where the engine's own player (afplay) is missing, run one of these by argv, first that works.
8const LINUX_PLAYERS = [
9 ['paplay'],
10 ['aplay', '-q'],
11 ['ffplay', '-nodisp', '-autoexit', '-loglevel', 'quiet'],
12]
13const WINDOWS_PLAYER = [
14 'powershell.exe',
15 '-NoProfile',
16 '-Command',
17 '(New-Object Media.SoundPlayer $env:DONE_SOUND).PlaySync()',
18]
19
20type Platform = 'mac' | 'linux' | 'windows'
21
22// `uname` answers on macOS and Linux; where it cannot start, this is Windows.
23function detect($: any): Promise<Platform> {
24 return $.process.run(['uname', '-s']).then(
25 (r: { stdout: string }) => (r.stdout.trim() === 'Darwin' ? 'mac' : 'linux'),
26 () => 'windows',
27 )
28}
29
30async function tryRun($: any, argv: string[], env?: Record<string, string>): Promise<boolean> {
31 const r = await $.process
32 .run(argv, { timeoutMs: 10_000, ...(env ? { env } : {}) })
33 .catch(() => undefined)
34 return r?.exitCode === 0
35}
36
37async function chime($: any, gain: number): Promise<void> {
38 const where = await detect($)
39 if (where === 'mac') {
40 await $.audio.play({ asset: CLIP }, { gain })
41 return
42 }
43 const file = `${$.plugin.root}/${CLIP}`
44 if (where === 'windows') {
45 await tryRun($, WINDOWS_PLAYER, { DONE_SOUND: file.replace(/\//g, '\\') })
46 return
47 }
48 for (const player of LINUX_PLAYERS) {
49 if (await tryRun($, [...player, file])) return
50 }
51}
52
53export const register: Register = (on, options) => {
54 const gain = typeof options.gain === 'number' ? options.gain : DEFAULT_GAIN
55 on('turn.complete', async ($, e, next) => {
56 const ran = await next(e)
57 const isLong = e.durationMs >= MIN_MS
58 if (e.agentId === undefined && e.reason !== 'aborted' && isLong) {
59 await chime($, gain).catch(() => {})
60 }
61 return ran
62 })
63}
64