Skip to content

Audio Notifications

Audio notifications play sound files and announce session names via text-to-speech when session statuses change. Sound packs provide themed audio, volume is configurable per-event, and multiple sound variants per event are randomly selected for variety. Notifications fire for both Claude Code and external agents (OpenCode, etc.).

  1. Create ~/.config/navi/sounds.yaml:
enabled: true
pack: starcraft
triggers:
waiting: true
permission: true
done: true
error: true
tts:
enabled: true
  1. Install a sound pack at ~/.config/navi/soundpacks/starcraft/ with audio files named by event (e.g., waiting.wav, done.mp3)

  2. Ensure a system audio player is installed (paplay, aplay, mpv, or ffplay on Linux; afplay on macOS)

  3. Notifications fire automatically when session statuses change.

Sound packs are directories of audio files organized by event at ~/.config/navi/soundpacks/:

~/.config/navi/soundpacks/
starcraft/
waiting.wav
waiting-1.wav # Multi-variant
waiting-2.wav # Multi-variant
permission.wav
done.wav
error-1.wav # Multi-variant
error-2.wav # Multi-variant
retro/
waiting.mp3
permission.mp3
done.mp3
error.mp3
  • Single: <event>.ext (e.g., done.wav)
  • Multi-variant: <event>-<N>.ext (e.g., waiting-1.wav, waiting-2.wav)
  • Supported formats: .wav, .mp3, .ogg, .flac

When multiple files exist for an event, one is randomly selected on each notification.

Press S in the session view to open the sound pack picker:

╭─────────────────────────────────────────╮
│ Sound Packs │
│ │
│ ✓ starcraft (4 events, 7 files) │
│ retro (4 events, 4 files) │
│ minimal (3 events, 3 files) │
│ │
│ ↑↓: navigate Enter: select p: │
│ preview Esc: close │
╰─────────────────────────────────────────╯
KeyAction
/ or k/jNavigate packs
EnterSelect pack (immediate hot-swap, persisted to config)
p or SpacePreview a random sound from highlighted pack
EscClose without changing

If no packs are installed, a helpful message directs you to the soundpacks directory.

Terminal window
navi sound list # List available packs with event/file counts
navi sound test <event> # Play configured sound for an event
navi sound test-all # Play all enabled trigger events sequentially
volume:
global: 80 # 0-100, default 100
events: # Per-event multiplier (0.0-1.0)
error: 1.0 # Errors at full configured volume
done: 0.7 # Done at 70% of global = 56 effective
waiting: 0.5 # Waiting at 50% of global = 40 effective

Effective volume = global * event_multiplier, clamped to 0-100. A volume of 0 skips playback entirely.

PlayerVolume Flag
paplay--volume=0-65536 (PulseAudio native)
pw-play--volume=0.0-1.0 (float)
afplay-v 0.0-1.0 (float)
mpv--volume=0-100 (percentage)
ffplay-volume 0-100 (percentage)
aplayNo volume flag (uses system mixer)

Press m in the session view to toggle mute. When muted:

  • No sounds or TTS play
  • “MUTED” indicator appears in the status line
  • Mute state is session-only (resets on restart)

The TUI polls sessions every 500ms. When a session’s status changes, the audio system:

  1. Checks if notifications are enabled and not muted
  2. Checks if the status has a trigger enabled
  3. Checks per-session cooldown (default 5 seconds)
  4. Resolves the sound file:
    • First: explicit files.<status> override in config
    • Then: active sound pack (random selection if multiple variants)
    • Otherwise: no sound
  5. Plays the sound with effective volume
  6. Speaks a TTS announcement after a 150ms delay (if TTS enabled)

All playback is non-blocking — audio runs in background goroutines. The first poll after startup is silent to avoid a burst of notifications.

Audio notifications also fire for external agent (OpenCode, etc.) status changes:

  • Each agent gets independent cooldown tracking via composite key (sessionName:agentType)
  • Same sound/TTS configuration applies to all agents
  • The background attach monitor also tracks agent state transitions
  • On first poll, agent states are initialized without firing notifications

Full configuration at ~/.config/navi/sounds.yaml:

enabled: true # Master toggle
pack: starcraft # Active sound pack name
volume: # Volume settings
global: 80 # 0-100, default 100
events: # Per-event multiplier (0.0-1.0)
error: 1.0
done: 0.7
waiting: 0.5
triggers: # Per-status enable/disable
waiting: true
permission: true
working: false
idle: false
stopped: false
done: true
error: true
files: # Explicit per-status file overrides
permission: ~/custom/alert.wav # Overrides pack for this event
tts:
enabled: true # Text-to-speech announcements
template: "{session} — {status}"
cooldown_seconds: 5 # Per-session cooldown interval
player: auto # Audio player: auto, paplay, afplay, aplay, mpv, ffplay
tts_engine: auto # TTS engine: auto, say, espeak-ng, espeak, spd-say
FieldTypeDefaultDescription
enabledboolfalseMaster toggle for all audio
packstringActive sound pack directory name
volume.globalint100Global volume (0-100)
volume.events.<status>float1.0Per-event volume multiplier (0.0-1.0)
triggers.<status>boolvariesWhether to notify on this status
files.<status>stringExplicit file override (takes precedence over pack)
tts.enabledbooltrueEnable text-to-speech announcements
tts.templatestring"{session} — {status}"TTS announcement template
cooldown_secondsint5Minimum seconds between notifications per session
playerstring"auto"Audio player binary or "auto" for detection
tts_enginestring"auto"TTS engine binary or "auto" for detection
StatusDefault
waitingenabled
permissionenabled
workingdisabled
idledisabled
stoppeddisabled
doneenabled
errorenabled

The files: config still works without sound packs. If both pack: and files: are set, explicit files: entries override the pack for those specific events.

When player: auto, navi searches for available backends:

PlatformDetection Order
macOSafplay (built-in)
Linuxpaplay (PulseAudio) → aplay (ALSA) → ffplay (FFmpeg) → mpv

When tts_engine: auto, navi searches for available backends:

PlatformDetection Order
macOSsay (built-in)
Linuxespeak-ngespeakspd-say (speech-dispatcher)

The TTS template supports {session} and {status} placeholders.

  • Missing config file: audio disabled, no errors
  • No audio player found: notifications silently skipped
  • No TTS engine found: sound plays but no speech
  • Missing sound file for a status: sound skipped, TTS still plays
  • No sound packs installed: pack picker shows helpful message
  • Malformed YAML: error returned, audio disabled