🔔 claude-code-fahh

Sound notifications for Claude Code. Pings when Claude needs you — and when Claude is done.
The two plugins
| Plugin | Plays when | Hook |
|---|
fahh | Claude needs your input (permission, multi-option question, plan approval) | Notification + PreToolUse |
done | Claude finishes responding to your turn | Stop |
Install one or both, depending on whether you want a heads-up before Claude blocks, an alert after Claude finishes, or both.
Why
Claude Code is great. Until you switch tabs, write a message in Slack, and come back 10 minutes later to find Claude stuck on a permission prompt — or to find Claude finished 8 minutes ago and you didn't notice.
fahh and done play a sound at exactly those two moments. No banners, no toasts, no setup beyond enabling the plugin.
fahh triggers (and only these)
| When | How |
|---|
| Permission prompt (e.g. "Claude needs your permission to use Write") | Notification hook with notification_type=permission_prompt |
| Multi-option question ("Which approach do you prefer? A / B / C") | PreToolUse on AskUserQuestion |
| Plan approval (exiting plan mode for review) | PreToolUse on ExitPlanMode |
Idle pings, completion notifications, and background events are filtered out — no sound spam.
done triggers (and only these)
| When | How |
|---|
| Turn finished (Claude stops responding, control returns to you) | Stop hook |
Subagent completions (SubagentStop) and hook-induced continuations (stop_hook_active=true) are filtered out so you only hear the sound once per turn.
Install
# 1. Add the marketplace
claude plugin marketplace add Dymyt-ry/claude-code-fahh
# 2. Install either or both plugins
claude plugin install fahh@claude-code-fahh
claude plugin install done@claude-code-fahh
Restart your Claude Code session and you're done. Next permission prompt → 🔔. Next finished turn → 🔔.
Configure
Each plugin has two optional environment variables:
fahh
| Env var | Default | Purpose |
|---|
FAHH_SOUND | ${CLAUDE_PLUGIN_ROOT}/assets/notify.mp3 | Path to a custom sound file. Supported formats depend on the available player (see Requirements). |
FAHH_DEBUG | 0 | Set to 1 to log every hook payload to /tmp/fahh-debug.log |
done
| Env var | Default | Purpose |
|---|
DONE_SOUND | ${CLAUDE_PLUGIN_ROOT}/assets/done.mp3 | Path to a custom sound file. Supported formats depend on the available player (see Requirements). |
DONE_DEBUG | 0 | Set to 1 to log every hook payload to /tmp/done-debug.log |
Set in your shell profile (~/.zshrc, ~/.bashrc):
export FAHH_SOUND="$HOME/sounds/my-ping.mp3"
export DONE_SOUND="$HOME/sounds/my-bell.mp3"
Requirements
- macOS —
afplay (built-in). Plays mp3, wav, aiff, ogg out of the box. ✅
- Linux (mp3 — bundled sound) — install
ffplay (ffmpeg) or mpg123. paplay works only if PulseAudio has the relevant codec plugins installed.
- Linux (WAV custom sound) — any of the above, plus
aplay (ALSA) will do. Do not rely on aplay for mp3 — it's a raw-PCM player and will fail silently on the bundled notify.mp3 / done.mp3. Either install a real mp3 player or point FAHH_SOUND / DONE_SOUND at a WAV file.
- Windows — not supported (yet). PRs welcome.
python3 is required for JSON parsing (preinstalled on macOS and most Linux distros).
The script tries players in this order and uses the first one available: afplay → ffplay → mpg123 → paplay → aplay.
Bundled sounds
plugins/fahh/assets/notify.mp3 — the "Claude needs you" ping.
plugins/done/assets/done.mp3 — the "Claude is done" chime.
Replace either with your own by setting FAHH_SOUND / DONE_SOUND, or overwrite the file directly.
Troubleshooting
Sound never plays. Verify the hook is loaded:
claude /hooks
You should see Notification: permission_prompt and PreToolUse: AskUserQuestion|ExitPlanMode (for fahh) and/or Stop (for done). If not, restart your Claude Code session — hooks load at session start.
Sound plays at weird times. Enable debug logging and inspect the corresponding log:
export FAHH_DEBUG=1 # for fahh
export DONE_DEBUG=1 # for done
# trigger Claude, then:
tail -50 /tmp/fahh-debug.log
tail -50 /tmp/done-debug.log
Open an issue with a redacted payload sample.