Prompt, walk away, and let Codex tap you on the shoulder — a sound when it finishes a task, and a different one when it's waiting on your approval. Here's the 2-minute setup, free, using a built-in macOS command and Codex's own hooks.
Add this to ~/.codex/hooks.json and restart Codex. You'll hear one sound when Codex finishes and a different one when it needs your approval:
{
"hooks": {
"Stop": [
{ "hooks": [ { "type": "command", "command": "afplay /System/Library/Sounds/Glass.aiff &" } ] }
],
"PermissionRequest": [
{ "hooks": [ { "type": "command", "command": "afplay /System/Library/Sounds/Funk.aiff &" } ] }
]
}
}
Codex CLI fires hooks on lifecycle events, and a hook just runs a shell command — the same model as Claude Code, in a global ~/.codex/hooks.json. Two events matter here:
Stop — fires when Codex finishes its turn (your task is done).PermissionRequest — fires when Codex asks to run a sensitive operation and needs your approval.afplay is macOS's built-in audio player — nothing to install. Point the two events at two different sounds and you've got distinct "done" and "needs you" alerts. That's the entire trick.
Codex's config.toml has a single notify slot, and the Codex desktop app already claims it. hooks.json is a separate file with a list of hooks per event — so it never fights the app's notifications, and it gives you a real PermissionRequest "needs you" event, not just "done".
~/.codex/hooks.json (create it if it doesn't exist). It's global, so it applies to every project."hooks" block from the TL;DR. If you already have a "hooks" key, merge the Stop and PermissionRequest entries into it rather than replacing it.Swap the system sound for any .aiff, .mp3, .wav, or .m4a on disk:
"command": "afplay ~/Sounds/codex-done.mp3 &"
The built-in system sounds live in /System/Library/Sounds/ — Glass, Funk, Ping, Hero, Submarine, Blow, and more. List them with:
ls /System/Library/Sounds/
Both events are recent additions to Codex CLI's hook system. Run a current Codex and you'll have all of them:
Stop — Codex CLI v0.114+PermissionRequest — Codex CLI v0.122+On older versions the unsupported event simply never fires — no errors, just no sound. Check your version with codex --version.
Run afplay /System/Library/Sounds/Glass.aiff directly in your terminal. If that's silent, it's your output device or volume — not the hook.
If the command works but the hook doesn't fire, make sure you restarted Codex after editing hooks.json and approved the trust prompt — hooks only load at session start, and Codex won't run an untrusted hook.
The hooks are identical everywhere Codex runs; only the sound command changes. Linux: paplay sound.wav or aplay sound.wav. Windows: powershell -c "(New-Object Media.SoundPlayer 'C:\path\sound.wav').PlaySync()".
The hook is simple — living with it is the annoying part. A single afplay ding gets old fast, you're hand-editing JSON, every machine needs the same setup, and "done" and "needs you" end up sounding the same. MacYaps is the convenience layer on the exact mechanism above — and it already speaks for Claude Code, so one app covers both agents.
hooks.json by hand — and it removes the hooks cleanly when you turn it off.Add a Stop hook to ~/.codex/hooks.json that runs a sound command like afplay /System/Library/Sounds/Glass.aiff. Codex fires the Stop event when it finishes a turn, so the sound plays every time a task completes. Restart Codex afterward so it loads the hook.
Yes — add a PermissionRequest hook pointing at a different sound. Codex fires it when it asks to run a sensitive operation, so you hear a distinct alert the moment it's waiting on you. (Requires Codex CLI v0.122+.)
It's ~/.codex/hooks.json — a global file that applies to every session, with the same JSON shape as Claude Code's settings.json hooks. It's separate from config.toml, so it doesn't conflict with the notify program the Codex desktop app uses.
Completely. The setup on this page is the whole mechanism and uses only afplay, which ships with macOS. MacYaps just automates it and adds voices and a UI.
Stop works on Codex CLI v0.114+ and PermissionRequest on v0.122+. On older versions the unsupported event simply never fires.
Yes — the hooks are the same; only the sound command changes (paplay/aplay on Linux, PowerShell's Media.SoundPlayer on Windows).