Get a desktop or Slack notification when Codex finishes a turn: notify and tui.notifications
Run a program when Codex CLI completes a turn with the notify setting, the JSON it receives, and how tui.notifications filters terminal notifications, with macOS/Linux examples.
Contents
Hand Codex a longer task, switch to something else, and you can easily miss the moment it finishes. Codex CLI offers two mechanisms: notify, which launches an external program when a turn completes, and tui.notifications, which controls the terminal's own notifications.
In short: write an argv array such as notify = ["python3", "/path/to/notify.py"], and Codex runs that program with a single JSON argument at the end of every turn. From there you can post a desktop notification, hit a Slack webhook, or anything else.
KEY POINT
What you will learn
- How to write
notifyand which JSON fields the program receives - Filtering terminal notifications by type, condition, and method with
tui.notifications - Desktop notification scripts for macOS and Linux
notify: launching an external program
In ~/.codex/config.toml, list the command as an array. Unset means disabled.
notify = ["python3", "/Users/you/.codex/notify.py"]
The program receives one JSON string as its argument and should exit 0 on success. The main fields:
| Field | Contents |
|---|---|
type | event type; currently agent-turn-complete |
thread-id | the session (thread) identifier |
turn-id | the turn identifier |
cwd | the working directory |
input-messages | the user messages for that turn |
last-assistant-message | the assistant's final output text |
用語解説
Turn: one round from you sending an instruction to Codex finishing its response, including any command runs and approvals in between. notify fires at the end of that unit.
macOS example
Using terminal-notifier (installable with Homebrew):
#!/usr/bin/env python3
import json, subprocess, sys
payload = json.loads(sys.argv[1])
if payload.get("type") != "agent-turn-complete":
sys.exit(0)
title = "Codex: turn complete"
body = (payload.get("last-assistant-message") or "")[:120]
subprocess.run(["terminal-notifier", "-title", title, "-message", body, "-group", payload.get("thread-id", "")])
Linux example
With notify-send you can skip the script entirely; the sample configuration shows this form.
notify = ["notify-send", "Codex"]
The JSON string then becomes the notification body as is. Insert a script like the one above if you want it tidier.
Sending to Slack
For a Slack Incoming Webhook, have the script trim last-assistant-message and POST it. Read the webhook URL from an environment variable rather than writing it into config.toml.
last-assistant-message contains your work
Passing last-assistant-message straight into a notification sends file names and code fragments to an external service. For Slack and similar, truncate to a few dozen characters or send only a "done" marker.
tui.notifications: controlling terminal notifications
The terminal's own notifications are configured under [tui].
[tui]
# true / false, or a list of types
notifications = ["agent-turn-complete", "approval-requested"]
# unfocused (default) | always
notification_condition = "unfocused"
# auto (default) | osc9 | bel
notification_method = "auto"
| Key | Values | Meaning |
|---|---|---|
notifications | true / false / list | on by default; restrict to agent-turn-complete and approval-requested |
notification_condition | unfocused / always | default fires only when the terminal is not focused |
notification_method | auto / osc9 / bel | auto prefers OSC 9 when the terminal supports it, else BEL (\x07) |
notify and tui.notifications are independent. For external notifications only, set notifications = false; to still hear about pending approvals, keep approval-requested in the list.
The overall structure of the config file is covered in the hub article Configuring Codex with config.toml, and when approvals are requested in Codex approval modes and sandbox settings.
Summary
notifyis an argv array, run with one JSON argument on everyagent-turn-complete- The JSON carries
type,thread-id,turn-id,cwd,input-messages, andlast-assistant-message terminal-notifieron macOS or a barenotify-sendon Linux is enough to start- Truncate the body before sending it to external services and keep webhook URLs in environment variables
- Terminal notifications are separate:
[tui]notifications,notification_condition,notification_method
FAQ
- Which events does notify receive?
- Currently agent-turn-complete. The program gets one JSON argument with type, thread-id, turn-id, cwd, input-messages, and last-assistant-message.
- Can I silence terminal notifications but keep notify?
- Yes. Set notifications = false under [tui]; notify is configured independently.
- Does notify work with the IDE extension?
- According to the notifications page, you configure notify on the Codex host the extension connects to.
Primary sources
This article was drafted by AI from official documentation and reviewed by the site operator before publishing. Found a mistake? Let us know via the contact page.