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.
- 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 - Codex hook support for
SessionStart,UserPromptSubmit,PermissionRequest, andPreCompact - Optional remote relay mode for VS Code Remote SSH and other SSH-based remote workflows
- 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 Codex hooks in
~/.codex/hooks.json - 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/bin
./install.sh --remote-endpoint=http://127.0.0.1:38765
./install.sh --remote-endpoint=http://127.0.0.1:38765 --remote-token=secret-tokenCodex:
~/.c-notify/sounds/codex/task-acknowledge/(from CodexUserPromptSubmit)~/.c-notify/sounds/codex/task-complete/~/.c-notify/sounds/codex/permission-needed/(from CodexPermissionRequest)~/.c-notify/sounds/codex/task-error/~/.c-notify/sounds/codex/context-compact/(from CodexPreCompact)~/.c-notify/sounds/codex/resource-limit/~/.c-notify/sounds/codex/session-start/(from CodexSessionStart)
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/context-compact/~/.c-notify/sounds/claude/resource-limit/
config.toml keeps notify for completion and enables the hooks engine:
notify = ["python3", "/ABSOLUTE/PATH/TO/c-notify/c-notify.py", "hook", "--tool", "codex"]
[features]
hooks = truehooks.json wires SessionStart, UserPromptSubmit, PermissionRequest, and PreCompact into c-notify:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "python3 /ABSOLUTE/PATH/TO/c-notify/c-notify.py hook --tool codex",
"timeout": 10,
"statusMessage": "Playing c-notify session-start sound"
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "python3 /ABSOLUTE/PATH/TO/c-notify/c-notify.py hook --tool codex",
"timeout": 10,
"statusMessage": "Playing c-notify task-acknowledge sound"
}
]
}
],
"PermissionRequest": [
{
"hooks": [
{
"type": "command",
"command": "python3 /ABSOLUTE/PATH/TO/c-notify/c-notify.py hook --tool codex",
"timeout": 10,
"statusMessage": "Playing c-notify permission-needed sound"
}
]
}
],
"PreCompact": [
{
"hooks": [
{
"type": "command",
"command": "python3 /ABSOLUTE/PATH/TO/c-notify/c-notify.py hook --tool codex",
"timeout": 10,
"statusMessage": "Playing c-notify context-compact sound"
}
]
}
]
}
}Notes:
- Codex
notifycurrently sendsagent-turn-completepayloads in normal operation. - Codex does not use message-semantic inference;
agent-turn-completealways routes totask-complete. - Codex hooks are currently used for
SessionStart,UserPromptSubmit,PermissionRequest, andPreCompact. - New or changed Codex hooks may require review in the TUI; open
/hooksand approve thec-notifyentries when Codex prompts for review. UserPromptSubmitmaps totask-acknowledge.PermissionRequestmaps topermission-needed.PreCompactmaps tocontext-compact;PostCompactis recognized as the same category but is not installed by default to avoid double playback.- The first
UserPromptSubmitimmediately followingSessionStartfor the same Codexsession_idis suppressed to avoid double playback on a brand-new or resumed session. Stopis intentionally not registered inhooks.json; completion already comes fromnotify, so wiring both would duplicate playback.PreToolUseandPostToolUseare intentionally not registered by default because they are high-frequency tool events.- Codex
task-errorandresource-limitremain explicit/manual categories unless Codex emits native events for them later. - Example files are included in
examples/codex-config.tomlandexamples/codex-hooks.json.
Every event uses the same command; only the event key and matcher differ:
{
"hooks": {
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "python3 /ABSOLUTE/PATH/TO/c-notify/c-notify.py hook --tool claude",
"timeout": 10,
"async": true
}
]
}
]
}
}Repeat the same structure for each event:
| Event | matcher |
|---|---|
SessionStart |
"" |
SessionEnd |
"" |
SubagentStart |
"" |
UserPromptSubmit |
"" |
Stop |
"" |
PermissionRequest |
"" |
PreCompact |
"" |
Notification is intentionally not registered; PermissionRequest is the only permission trigger.
Remote mode is optional. If you do nothing, c-notify keeps working exactly as a local-only tool.
Use remote mode when Codex or Claude runs on a remote machine but you want the sound to play on your local machine.
- On your local machine, start the receiver:
c-notify serve --listen 127.0.0.1 --port 38765- Add an SSH reverse tunnel on your local machine for that remote host:
Host my-remote
HostName your.remote.host
User your_user
RemoteForward 127.0.0.1:38765 127.0.0.1:38765- On the remote machine, install relay-based hooks instead of local playback hooks:
./install.sh --remote-endpoint=http://127.0.0.1:38765- Optional: protect the receiver with a token on both sides:
# local machine
c-notify serve --listen 127.0.0.1 --port 38765 --token secret-token
# remote machine
./install.sh --remote-endpoint=http://127.0.0.1:38765 --remote-token=secret-tokenNotes:
install.shonly switches to relay mode when you explicitly pass--remote-endpoint.- Local installs remain direct-playback installs.
- The receiver exposes
GET /healthzand acceptsPOST /hook/codexandPOST /hook/claude. - Full setup guide:
docs/VS_CODE_REMOTE_SSH.md
Related files:
| Hook examples | Autostart (systemd) | Autostart (launchd) | |
|---|---|---|---|
| Codex | codex-config-remote.toml, codex-hooks-remote.json |
c-notify-serve.service |
com.c-notify.serve.plist |
| Claude | claude-hooks-remote.json |
c-notify-tunnel.service |
com.c-notify.tunnel.plist |
Setup:
./install.sh
./c-notify init
./c-notify init --refresh-readmesControl:
./c-notify on
./c-notify off
./c-notify toggle
./c-notify status
./c-notify events
./c-notify events --tool codex
./c-notify events --tool claudeManual playback:
./c-notify play --tool claude --event task-complete
./c-notify play --tool codex --event task-acknowledgeDebug:
./c-notify hook --tool codex --event session-start --debug
./c-notify hook --tool codex --debug
./c-notify hook --tool claude --debug
tail -n 40 ~/.c-notify/logs/hook-events.jsonlRemote:
./c-notify serve --listen 127.0.0.1 --port 38765
./c-notify relay --tool codex --endpoint http://127.0.0.1:38765 --debugRuntime config path:
~/.c-notify/config.json~/.c-notify/state.json~/.c-notify/logs/hook-events.jsonl
Optional override:
C_NOTIFY_HOME=/custom/pathto relocate config/state/sounds root.C_NOTIFY_INSTALL_HOME=/custom/hometo relocate install targets used byinstall.sh.C_NOTIFY_REMOTE_ENDPOINT=http://127.0.0.1:38765to defaultrelayand remote install wiring to one receiver.C_NOTIFY_REMOTE_TOKEN=secret-tokento reuse the same token forserve,relay, and remote install wiring.
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
Hook debug log:
~/.c-notify/logs/hook-events.jsonlrecords recent hook invocations for Codex and Claude- payload text is truncated to keep entries small
- rotation is automatic:
256 KiB x 4 files
- macOS:
afplay - Linux:
pw-play,paplay,ffplay,aplay(fallback order)
Windows support is intentionally out of scope for this first version.