c-notify is a lightweight local hook sound router for Codex and Claude Code.
It plays a random audio file from ~/.c-notify/sounds/<tool>/<event>/ when hook events arrive.
Audio files are user-provided. The repository does not bundle sound assets.
简体中文文档: README_ZH.md
- Tool-specific event namespaces (
codexandclaude) with independent event sets - Random playback from per-event folders
- Global portable switch:
on / off / toggle / status - Linux/macOS playback backend support
- Event folder bootstrap with bilingual
README.mdper folder - Deterministic Codex routing:
agent-turn-completemaps directly totask-complete
cd c-notify
chmod +x c-notify c-notify.py
./c-notify init
./c-notify status
./c-notify eventscd c-notify
chmod +x install.sh
./install.shWhat install.sh does:
- Installs
c-notifyto~/.local/bin/c-notify(symlink) - Appends a PATH block to your shell rc file (
~/.zshrcfor zsh,~/.bashrc/~/.bash_profilefor bash) - Writes/updates Codex notify config in
~/.codex/config.toml - Writes/updates Claude hooks in
~/.claude/settings.json
Useful flags:
./install.sh --no-codex
./install.sh --no-claude
./install.sh --no-path
./install.sh --bin-dir=/custom/binCore categories (Codex):
~/.c-notify/sounds/codex/task-complete/~/.c-notify/sounds/codex/permission-needed/~/.c-notify/sounds/codex/task-error/~/.c-notify/sounds/codex/context-compact/~/.c-notify/sounds/codex/resource-limit/~/.c-notify/sounds/codex/session-start/(optional/manual)
Core categories (Claude):
~/.c-notify/sounds/claude/session-start/~/.c-notify/sounds/claude/session-end/(optional)~/.c-notify/sounds/claude/subagent-start/(optional)~/.c-notify/sounds/claude/task-acknowledge/~/.c-notify/sounds/claude/task-complete/~/.c-notify/sounds/claude/permission-needed/~/.c-notify/sounds/claude/task-error/~/.c-notify/sounds/claude/context-compact/~/.c-notify/sounds/claude/resource-limit/
notify = ["python3", "/ABSOLUTE/PATH/TO/c-notify/c-notify.py", "hook", "--tool", "codex"]Notes:
- Codex
notifycurrently sendsagent-turn-completepayloads in normal operation. - Codex does not use message-semantic inference;
agent-turn-completealways routes totask-complete. - Codex
task-error/resource-limit/context-compactare explicit/manual categories unless Codex emits native events in the future. approval-requestedis kept as a future/manual compatibility alias and maps topermission-neededif such payloads are provided.- For real-time approval cues today, use Codex TUI notifications (
[tui] notifications = ["approval-requested"]) rather than relying onnotify.
Use one command entry for all events:
{
"hooks": {
"SessionStart": [
{ "matcher": "", "hooks": [ { "type": "command", "command": "python3 /ABSOLUTE/PATH/TO/c-notify/c-notify.py hook --tool claude", "timeout": 10, "async": true } ] }
],
"SessionEnd": [
{ "matcher": "", "hooks": [ { "type": "command", "command": "python3 /ABSOLUTE/PATH/TO/c-notify/c-notify.py hook --tool claude", "timeout": 10, "async": true } ] }
],
"SubagentStart": [
{ "matcher": "", "hooks": [ { "type": "command", "command": "python3 /ABSOLUTE/PATH/TO/c-notify/c-notify.py hook --tool claude", "timeout": 10, "async": true } ] }
],
"UserPromptSubmit": [
{ "matcher": "", "hooks": [ { "type": "command", "command": "python3 /ABSOLUTE/PATH/TO/c-notify/c-notify.py hook --tool claude", "timeout": 10, "async": true } ] }
],
"Stop": [
{ "matcher": "", "hooks": [ { "type": "command", "command": "python3 /ABSOLUTE/PATH/TO/c-notify/c-notify.py hook --tool claude", "timeout": 10, "async": true } ] }
],
"PermissionRequest": [
{ "matcher": "", "hooks": [ { "type": "command", "command": "python3 /ABSOLUTE/PATH/TO/c-notify/c-notify.py hook --tool claude", "timeout": 10, "async": true } ] }
],
"PostToolUseFailure": [
{ "matcher": "Bash", "hooks": [ { "type": "command", "command": "python3 /ABSOLUTE/PATH/TO/c-notify/c-notify.py hook --tool claude", "timeout": 10, "async": true } ] }
],
"PreCompact": [
{ "matcher": "", "hooks": [ { "type": "command", "command": "python3 /ABSOLUTE/PATH/TO/c-notify/c-notify.py hook --tool claude", "timeout": 10, "async": true } ] }
]
}
}Notification is intentionally not registered; PermissionRequest is the only permission trigger.
List current known events:
./c-notify events
./c-notify events --tool codex
./c-notify events --tool claude./install.sh
./c-notify init
./c-notify init --refresh-readmes
./c-notify on
./c-notify off
./c-notify toggle
./c-notify status
./c-notify events --tool claude
./c-notify play --tool claude --event task-complete
./c-notify hook --tool codex --debug
./c-notify hook --tool claude --debugRuntime config path:
~/.c-notify/config.json
Optional override:
C_NOTIFY_HOME=/custom/pathto relocate config/state/sounds root.C_NOTIFY_INSTALL_HOME=/custom/hometo relocate install targets used byinstall.sh.
Important keys:
enabled: master on/off switchsound_root: default~/.c-notify/soundsvolume: playback volume (backend dependent)extensions: allowed audio extensionsprevent_overlap: skip new playback while prior process is alivecooldown_secondsandcooldown_by_event: optional throttlinghook_strict_exit: defaultfalse; whentrue, hook exits non-zero for unmapped/no-sound outcomes
- macOS:
afplay - Linux:
pw-play,paplay,ffplay,aplay(fallback order)
Windows support is intentionally out of scope for this first version.