macOS desktop notifications for Codex CLI, focused on the two moments that matter most in day-to-day use:
- a turn finishes
- Codex needs your approval or manual input
Chinese version:
This project combines two notification paths because current Codex CLI behavior is split:
agent-turn-completeis available through the officialnotifyhook.approval-requestedanduser-input-requestedare exposed through the TUI notification channel, not thenotifyhook.
So the implementation uses a hybrid design:
codex-notify.mjshandles completed-turn events and forwards them to macOS Notification Center withosascript.- Codex TUI config handles approval and input prompts when the terminal window is not focused.
codex-notify.mjsReceives the JSON payload from Codex, normalizes the event, appends an optional local log, and sends a macOS notification.codex-with-notifyA wrapper that startscodexwith the required config overrides.codex-ghostty-notifyA Ghostty-specific wrapper for interactive Codex sessions. It tracks the launching Ghostty tab and sends notifications when approval or input requests stay pending while you are no longer on that tab.codex-ghostty-notify.mjsA local WebSocket proxy that sits betweencodex --remoteand a localcodex app-server, intercepts approval events, and applies Ghostty tab-aware notification rules.IMPLEMENTATION_NOTES.mdDesign summary, tradeoffs, validation method, and lessons learned.PROJECT_CHANGE_SUMMARY.zh-CN.mdA Chinese project evolution summary that records the major changes, rationale, usage patterns, and lessons from this implementation process.
Codex can invoke an external program via its notify setting. The program receives a JSON payload describing the finished turn. This project uses:
notify = ["node", "/path/to/codex-notify.mjs"]The script then:
- parses the event payload
- extracts a concise title and body
- optionally writes a JSON line to a local log
- calls
/usr/bin/osascriptto show a native macOS notification
Current Codex builds surface these through TUI notifications. This project enables:
[tui]
notifications = ["approval-requested", "user-input-requested"]
notification_method = "auto"This means:
- if the terminal is unfocused, macOS can surface a notification through the terminal app
- if the terminal is focused, the prompt appears inline and no extra alert is needed
/Users/liufei/Downloads/VibeCoding/codex-desktop-notify/codex-with-notifyOr pass a prompt:
/Users/liufei/Downloads/VibeCoding/codex-desktop-notify/codex-with-notify "explain this repo"If you use Ghostty and want approval notifications even when Ghostty itself is still frontmost but you switched to another tab, use:
/Users/liufei/Downloads/VibeCoding/codex-desktop-notify/codex-ghostty-notifyOptional alias:
alias cg='/Users/liufei/Downloads/VibeCoding/codex-desktop-notify/codex-ghostty-notify'Then start interactive Codex with:
cgThis wrapper:
- starts a local
codex app-server - starts
codex --remoteagainst a local proxy - captures approval and user-input requests from the app-server protocol
- checks the currently selected Ghostty tab through AppleScript
- sends a macOS notification whenever the request is still pending and you are no longer on the launching tab
Plain codex is unchanged. It keeps the previous behavior.
If you want plain codex to always notify, add this to ~/.codex/config.toml:
notify = ["node", "/Users/liufei/Downloads/VibeCoding/codex-desktop-notify/codex-notify.mjs"]
[tui]
notifications = ["approval-requested", "user-input-requested"]
notification_method = "auto"CODEX_NOTIFY_LOG=/tmp/codex-notify.log \
CODEX_NOTIFY_DISABLE_OSASCRIPT=1 \
node /Users/liufei/Downloads/VibeCoding/codex-desktop-notify/codex-notify.mjs \
'{"type":"agent-turn-complete","turn-id":"demo-1","cwd":"/Users/liufei/Downloads","last-assistant-message":"Done."}'Expected result:
- one JSON log line is appended to
/tmp/codex-notify.log - the event type is
agent-turn-complete
Run:
CODEX_NOTIFY_LOG=/tmp/codex-notify.log codexThen:
- trigger a normal task and switch away from the terminal before the turn ends
- trigger a command that needs approval and switch away before the approval prompt appears
Expected result:
- completed turns notify through the hook script
- approval and input requests notify through the TUI channel
approval-requestedcurrently does not reach thenotifyhook, so approvals cannot yet be handled by the same script path as completions.- approval notifications depend on terminal and macOS notification support while the terminal is unfocused.
codex-ghostty-notifyalready uses a localapp-serverbridge for interactive Ghostty sessions, but plaincodexstill follows the simpler hook plus TUI design.- if you want the same tab-aware behavior in terminals other than Ghostty, the next step is a generalized
app-serverbridge that can detect session focus outside Ghostty. codex-ghostty-notifyis designed for interactive Ghostty sessions. For non-interactive commands such ascodex exec, it falls back to plaincodex.- the Ghostty wrapper relies on Ghostty AppleScript support. In Ghostty this is controlled by
macos-applescript, which defaults totrue.