Codex is really slow, with it’s high reasoning model much slower than Claude Opus.
Codex CLI is still great… until it asks for approval or finishes a long task and you miss it because there’s no obvious “ping me” setup.
Here’s the setup that made notifications work on macOS:
- OSC 9 notifications via iTerm2 (Codex TUI → macOS Notification Center)
- A
notifyhook that callsterminal-notifier(with sound)
Oh yea, not sure why they didn’t bake notification in.
1) Enable iTerm2 “escape sequence” notifications
In iTerm2, go to:
Settings → Profiles → Terminal → Notifications → Enable ✅ Send escape sequence-generated alerts
This matters because Codex can emit OSC 9 notifications, and iTerm2 can turn those into Notification Center alerts.
Quick OSC9 sanity test
Run this in iTerm2:
printf '\e]9;OSC9 test notification\a'
If you see a macOS notification, iTerm2 is correctly configured.
The iTerm notification is good for Approval prompts (approval-requested).
2) Configure Codex TUI notifications (approval + completion)
Codex’s tui.notification_method is specifically for unfocused terminal notifications (so don’t be surprised if it only pops when you’ve Cmd-Tabbed away).
Add this to ~/.codex/config.toml:
[tui]
notifications = ["agent-turn-complete", "approval-requested"]
notification_method = "osc9"
3) Add the notify hook (always-on completion + sound)
Codex supports a notify hook to run a program when supported events fire — currently only agent-turn-complete.
Important TOML gotcha
Codex’s sample config explicitly notes: Root keys must appear before tables in TOML.
So put notify = ... near the top (before any [tui], [mcp_servers.*], etc).
Example ~/.codex/config.toml skeleton:
# Root keys (keep these before any [tables])
notify = ["/bin/bash", "/Users/YOU/.codex/hooks/notify.sh"]
4) The notify script (the “actually works every time” version)
Install terminal-notifier with brew install terminal-notifier
Create: ~/.codex/hooks/notify.sh
#!/bin/bash
set -euo pipefail
# Hooks often run with a minimal PATH
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
NOTIFIER="$(command -v terminal-notifier || true)"
LOG="/tmp/codex-notify.log"
# Log for debugging (and to avoid spam in the Codex UI)
exec >>"$LOG" 2>&1
echo "----- $(date) -----"
echo "argv: $*"
TITLE="Codex CLI"
MESSAGE="Codex finished a task."
# Codex passes JSON as argv[1] (not stdin)
PAYLOAD="${1:-}"
if [[ -n "$PAYLOAD" ]]; then
MSG_FROM_JSON="$(python3 - <<'PY' "$PAYLOAD" 2>/dev/null || true
import json,sys
j=json.loads(sys.argv[1])
print((j.get("last-assistant-message") or "").strip())
PY
)"
if [[ -n "${MSG_FROM_JSON:-}" ]]; then
MESSAGE="$MSG_FROM_JSON"
fi
fi
# terminal-notifier can treat messages starting with '-' like flags (and show help)
if [[ "$MESSAGE" == "-"* ]]; then
MESSAGE="• ${MESSAGE#- }"
fi
# Optional: trim super long messages
MESSAGE="${MESSAGE:0:220}"
if [[ -n "$NOTIFIER" ]]; then
"$NOTIFIER" \
-title "$TITLE" \
-message "$MESSAGE" \
-sound default \
-activate "com.googlecode.iterm2" \
>/dev/null 2>&1 || true
else
echo "terminal-notifier not found" >&2
fi
Make it executable chmod +x ~/.codex/hooks/notify.sh.
Test it manually
~/.codex/hooks/notify.sh '{"last-assistant-message":"- done (test)"}'
tail -n 20 /tmp/codex-notify.log
If that shows a notification, your script is fine.
That’s it. With OSC9 (iTerm2) you’ll catch approval prompts, and with the notify hook you’ll get a dependable completion alert + sound (even if you’re not watching the terminal).