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 notify hook that calls terminal-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).