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)
By default, Codex sends TUI notifications when the terminal is unfocused (so don’t be surprised if it only pops when you’ve Cmd-Tabbed away). tui.notification_method selects the delivery method; tui.notification_condition controls whether focus matters.
Update, 7 October 2026: the current configuration reference documents notification_condition = "always" under [tui] if you want alerts regardless of focus. Leave it unset to keep the default "unfocused" behavior. Your terminal and macOS notification settings still decide how an alert is displayed.
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).