Play a sound when Codex finishes a prompt / task

Open 💬 53 comments Opened Sep 20, 2025 by khryniewiecki
💡 Likely answer: A maintainer (etraut-openai, contributor) responded on this thread — see the highlighted reply below.

Sound when Codex finishes

Summary
Please add an optional, clearly audible completion sound that plays when Codex finishes executing a prompt/task (useful when prompts run longer and the user switches focus).

Why
Helps when Codex works in background and I’m in another window/tab.
Terminal bell \a is too short/quiet and easy to miss.

Use case / Motivation
Most of my prompts take Codex quite a long time to execute — often more than a minute. It’s frustrating that I have to sit and stare at the screen just to know when it’s done. Since I work remotely, I could easily use that waiting time to do something else, as long as Codex informs me when it finishes.

Yes, I can (and already did) hack together my own workaround, but it would be much nicer if this were a built-in feature of the extension. For example, after finishing a prompt Codex could simply play a clear sound notification.

Environment
VSC

Extras
I would be cool if it plays: https://youtu.be/CduA0TULnow?list=RDCduA0TULnow&t=58

Are you interested in implementing this feature?

_No response_

Additional information

_No response_

View original on GitHub ↗

53 Comments

robin-liquidium · 10 months ago

you can already do that by setting up a notification hook for it in your .codex config.toml.

just add this to your config.toml
notify = ["python3", "~/.codex/notify.py"]

and setup a notify.py file

#!/usr/bin/env python3
"""
Minimal notifier for Codex "notify" hook.
Plays a short sound when an agent turn completes.

Usage: Codex runs this script with a single JSON argument.
"""

import json
import os
import subprocess
import sys


def main() -> int:
    if len(sys.argv) != 2:
        return 0

    try:
        event = json.loads(sys.argv[1])
    except Exception:
        event = {}

    if event.get("type") != "agent-turn-complete":
        return 0

    # Prefer a built-in macOS sound if available; fall back to TTS.
    sound_path = "/System/Library/Sounds/Glass.aiff"
    try:
        if os.path.exists(sound_path):
            subprocess.Popen(["afplay", sound_path])
        else:
            subprocess.Popen(["say", "Codex turn complete"])  # best-effort fallback
    except Exception:
        pass

    return 0


if __name__ == "__main__":
    raise SystemExit(main())
markgrover · 9 months ago

I had to a make a few tweaks (iterm2 on macOS):

  • Relative path didn't work for me in my ~/.codex/config.toml, I had to use absolute path to my home dir

notify = ["python3", "/Users/YOUR_USER_NAME/.codex/notify.py"]

  • Minor change to notify.py to use the terminal bell instead:
#!/usr/bin/env python3
"""
Minimal notifier for Codex "notify" hook.
Plays a short sound when an agent turn completes.

Usage: Codex runs this script with a single JSON argument.
"""

import sys
import json

def main():
    if len(sys.argv) != 2:
        return 0

    try:
        event = json.loads(sys.argv[1])
    except Exception:
        return 0

    if event.get("type") != "agent-turn-complete":
        return 0

    # Print BEL character -> terminal bell
    sys.stdout.write("\a")
    sys.stdout.flush()
    return 0

if __name__ == "__main__":
    raise SystemExit(main())
  • To test once it's all set up run:

python3 ~/.codex/notify.py '{"type":"agent-turn-complete"}'

samlouiscohen · 9 months ago

Love this ask. I implemented a cross-platform MVP that’s opt-in and uses system sounds (no bundled assets).

Proposal

  • notifications.sound = true|false (default false)
  • notifications.only_on_long_runs_ms = 0
  • notifications.success_tone = "default" / notifications.error_tone = "default"
  • Maps to OS-appropriate system sounds:
  • macOS → Hero (success), Basso (error)
  • Windows → SystemSounds.Asterisk (success), SystemSounds.Hand (error)
  • Linux → canberra id complete (success), dialog-error (error)
  • Fallback: terminal bell (\a) if no backend available
  • Fires only on final agent-turn-complete

Why

  • Improves the UX for long-running tasks without requiring custom notify hook scripts
  • Only system sounds → zero new binary assets
  • Safe defaults, fully optional, and configurable via ~/.codex/config.toml
  • Increasingly relevant as model time horizons grow longer — Codex runs already take tens of minutes in some cases. An audible success/failure cue makes it possible to safely multitask while waiting.

Example Config

[notifications]
sound = true
only_on_long_runs_ms = 10000
success_tone = "default"
error_tone = "default"

Opened a PR to implement this: #4329

It adds:

  • notifications.sound (default off)
  • notifications.only_on_long_runs_ms
  • success_tone / error_tone with sensible per-OS defaults
  • Fallback to terminal bell

Cross-platform mappings:

  • macOS → Hero (success), Basso (error)
  • Windows → SystemSounds.Asterisk / Hand
  • Linux → canberra complete / dialog-error
atreeon · 9 months ago

Would this proposal result in the cli playing a sound if the task pauses asking for approval?

jsh9 · 9 months ago

Hi @samlouiscohen , does your code change work for the VS Code extension as well? Or only the CLI version of Codex?

budingkak12 · 9 months ago

我尝试了很多次,都通知失败,最后,我把notify = ["python3", "/Users/wang/.codex/notify.py"]
放到disable_response_storage = true的后面一行 成功了,(我之前一直放在
trust_level = "trusted"
后面,导致一直失败,很神奇)

budingkak12 · 9 months ago

"I tried many times, but the notification failed every time. Finally, I moved the line notify = ["python3", "/Users/wang/.codex/notify.py"] right after disable_response_storage = true, and it worked! (It's strange because it kept failing when I had it after trust_level = "trusted".)"

rijieli · 9 months ago

For macOS users, I found a simple way to play sounds or notifications using the Shortcuts app, which has higher permissions. I configured the TOML file accordingly.

notify = ["zsh", "/Users/roger/.codex/notify.sh"]

[tui]
notifications = true
#!/bin/zsh
/usr/bin/shortcuts run "Codex Done"

<img width="925" height="790" alt="Image" src="https://github.com/user-attachments/assets/b32afc67-cd71-4762-88bf-5815455861d5" />

jsh9 · 9 months ago

The following worked for me on macOS, simple and easy to maintain, without the hassle of creating a Python script (notify.py) or a Shortcut.

In ~/.codex/config.toml, add this line:

model = "gpt-5-codex"
model_reasoning_effort = "low"
+ notify = ["bash", "-lc", "afplay /System/Library/Sounds/Bottle.aiff"]

/System/Library/Sounds has many different ringtones for you to choose. You don't have to use my choice.

/System/Library/Sounds
├── Basso.aiff
├── Blow.aiff
├── Bottle.aiff
├── Frog.aiff
├── Funk.aiff
├── Glass.aiff
├── Hero.aiff
├── Morse.aiff
├── Ping.aiff
├── Pop.aiff
├── Purr.aiff
├── Sosumi.aiff
├── Submarine.aiff
└── Tink.aiff

(Inspired by https://github.com/openai/codex/issues/4998#issuecomment-3386645329)

Ahmet-Dedeler · 8 months ago

Will they add this?

antonurankar-moloco · 7 months ago

@Ahmet-Dedeler Just ask the Codex to do a terminal beep after a task.

huangwb8 · 7 months ago
you can already do that by setting up a notification hook for it in your .codex config.toml. just add this to your config.toml notify = ["python3", "~/.codex/notify.py"] and setup a notify.py file `` #!/usr/bin/env python3 """ Minimal notifier for Codex "notify" hook. Plays a short sound when an agent turn completes. Usage: Codex runs this script with a single JSON argument. """ import json import os import subprocess import sys def main() -> int: if len(sys.argv) != 2: return 0 try: event = json.loads(sys.argv[1]) except Exception: event = {} if event.get("type") != "agent-turn-complete": return 0 # Prefer a built-in macOS sound if available; fall back to TTS. sound_path = "/System/Library/Sounds/Glass.aiff" try: if os.path.exists(sound_path): subprocess.Popen(["afplay", sound_path]) else: subprocess.Popen(["say", "Codex turn complete"]) # best-effort fallback except Exception: pass return 0 if __name__ == "__main__": raise SystemExit(main()) ``

This script helps a lot. Here is a similar script generated by gpt-5-mini. It also works in macOS.

#!/usr/bin/env python3
"""
Notifier for Codex "notify" hook (updated).

Behavior changes:
- For every event with type == "agent-turn-complete", the script will now:
    * play a sound (as before)
    * show a system notification (new: regardless of whether it's the final turn)
- If the event heuristically indicates the conversation ended, the notification title/message
  will reflect that; otherwise a shorter "Agent turn complete" notification is shown.
- Cross-platform best-effort: macOS (osascript), Linux (notify-send / DBus), Windows (PowerShell NotifyIcon).
- External calls are wrapped in try/except so the hook never crashes.
"""

import json
import os
import platform
import shutil
import subprocess
import sys
from typing import Any, Dict


def shutil_which(cmd: str) -> bool:
    """Return True if command is found in PATH (wrapper around shutil.which)."""
    try:
        return shutil.which(cmd) is not None
    except Exception:
        return False


def play_sound():
    """Play a short notification sound. Prefer macOS system sound; fallback to TTS or other utilities."""
    system = platform.system()
    try:
        if system == "Darwin":
            sound_path = "/System/Library/Sounds/Glass.aiff"
            if os.path.exists(sound_path) and shutil_which("afplay"):
                subprocess.Popen(["afplay", sound_path])
                return
            if shutil_which("say"):
                subprocess.Popen(["say", "Codex turn complete"])
                return
        elif system == "Linux":
            # Try common desktop sounds or TTS utilities
            if shutil_which("paplay") and os.path.exists("/usr/share/sounds/freedesktop/stereo/complete.oga"):
                subprocess.Popen(["paplay", "/usr/share/sounds/freedesktop/stereo/complete.oga"])
                return
            if shutil_which("aplay") and os.path.exists("/usr/share/sounds/alsa/Front_Center.wav"):
                subprocess.Popen(["aplay", "/usr/share/sounds/alsa/Front_Center.wav"])
                return
            if shutil_which("espeak"):
                subprocess.Popen(["espeak", "Codex turn complete"])
                return
            if shutil_which("spd-say"):
                subprocess.Popen(["spd-say", "Codex turn complete"])
                return
        elif system == "Windows":
            # Use PowerShell SAPI speak as a fallback
            ps = 'Add-Type –AssemblyName System.Speech; $s = New-Object System.Speech.Synthesis.SpeechSynthesizer; $s.Speak("Codex turn complete");'
            subprocess.Popen(["powershell", "-NoProfile", "-Command", ps])
            return
    except Exception:
        # ignore any failures
        pass


def show_notification(title: str, message: str, subtitle: str = ""):
    """
    Show a system notification in a cross-platform, best-effort way.
    - macOS: use osascript display notification (supports subtitle and sound)
    - Linux: try notify-send (libnotify) or gi.repository.Notify
    - Windows: use a PowerShell snippet with System.Windows.Forms.NotifyIcon (balloon)
    """
    system = platform.system()

    try:
        if system == "Darwin":
            # Escape quotes and backslashes
            esc_msg = message.replace("\\", "\\\\").replace('"', '\\"')
            esc_title = title.replace("\\", "\\\\").replace('"', '\\"')
            esc_sub = subtitle.replace("\\", "\\\\").replace('"', '\\"')
            script = f'display notification "{esc_msg}" with title "{esc_title}"'
            if esc_sub:
                script += f' subtitle "{esc_sub}"'
            script += ' sound name "Glass"'
            subprocess.Popen(["osascript", "-e", script])
            return

        elif system == "Linux":
            # Try notify-send (libnotify)
            if shutil_which("notify-send"):
                subprocess.Popen(["notify-send", title, message, "-u", "normal", "-t", "10000"])
                return
            # Try Python DBus/GObject notification (best-effort)
            try:
                from gi.repository import Notify  # type: ignore
                Notify.init("Codex")
                n = Notify.Notification.new(title, message, None)
                n.set_timeout(10000)
                n.show()
                return
            except Exception:
                pass

        elif system == "Windows":
            # Create a balloon tip via System.Windows.Forms.NotifyIcon
            esc_title = title.replace("'", "''")
            esc_message = message.replace("'", "''")
            ps_script = (
                "$title = '{t}'; $msg = '{m}'; "
                "[void][Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); "
                "[void][Reflection.Assembly]::LoadWithPartialName('System.Drawing'); "
                "$notify = New-Object System.Windows.Forms.NotifyIcon; "
                "$notify.Icon = [System.Drawing.SystemIcons]::Information; "
                "$notify.BalloonTipTitle = $title; "
                "$notify.BalloonTipText = $msg; "
                "$notify.Visible = $true; "
                "$notify.ShowBalloonTip(10000); "
                "Start-Sleep -Seconds 6; "
                "$notify.Dispose();"
            ).format(t=esc_title, m=esc_message)
            subprocess.Popen(["powershell", "-NoProfile", "-Command", ps_script])
            return

    except Exception:
        # Ignore notification errors and fall back to printing
        pass

    # Final fallback: print to stderr
    try:
        print(f"[Notification] {title}: {message}", file=sys.stderr)
    except Exception:
        pass


def event_signals_conversation_end(event: Dict[str, Any]) -> bool:
    """
    Heuristic to determine whether the event indicates the conversation ended.
    Checks many common fields to be tolerant of different event shapes.
    """
    if not isinstance(event, dict):
        return False

    # Direct boolean flags
    for key in ("is_last_turn", "conversation_end", "conversation_finished", "final", "closed"):
        if key in event and bool(event.get(key)):
            return True

    # Check nested dicts
    for container_key in ("payload", "metadata", "data", "details"):
        sub = event.get(container_key)
        if isinstance(sub, dict):
            for key in ("conversation_end", "conversation_finished", "is_last_turn", "final"):
                if key in sub and bool(sub.get(key)):
                    return True

    # State string
    state = event.get("conversation_state") or event.get("state")
    if isinstance(state, str):
        if state.lower() in ("finished", "ended", "closed", "complete"):
            return True

    # turn/total heuristics
    try:
        turn = event.get("turn")
        total = event.get("total_turns") or event.get("turns_total") or event.get("total_turns_estimate")
        if isinstance(turn, int) and isinstance(total, int) and turn >= total:
            return True
    except Exception:
        pass

    return False


def build_notification_text(event: Dict[str, Any]) -> (str, str, str):
    """
    Build (title, message, subtitle) for the notification based on the event.
    This is called for every agent-turn-complete event (so notifications always appear).
    """
    # Detect end vs non-end
    is_end = event_signals_conversation_end(event)

    if is_end:
        title = "Codex — Conversation finished"
        summary = (
            event.get("summary")
            or event.get("message")
            or (event.get("payload") or {}).get("summary")
            or (event.get("metadata") or {}).get("summary")
            or event.get("text")
            or "The conversation has ended."
        )
        msg = str(summary)
        if len(msg) > 240:
            msg = msg[:236] + "..."
        subtitle = event.get("agent") or (event.get("metadata") or {}).get("agent") or ""
        return title, msg, str(subtitle)
    else:
        # Non-final agent turn: show a short notification
        title = "Codex — Agent turn complete"
        # Prefer a short snippet if available
        snippet = (
            event.get("message")
            or event.get("text")
            or (event.get("payload") or {}).get("snippet")
            or (event.get("metadata") or {}).get("snippet")
            or event.get("summary")
            or "An agent finished a turn."
        )
        msg = str(snippet)
        if len(msg) > 200:
            msg = msg[:196] + "..."
        subtitle = event.get("agent") or ""
        return title, msg, str(subtitle)


def main() -> int:
    # Expect exactly one JSON argument (same pattern as original)
    if len(sys.argv) != 2:
        return 0

    try:
        event = json.loads(sys.argv[1])
    except Exception:
        event = {}

    # Only act on agent-turn-complete events (preserve original behavior)
    if event.get("type") != "agent-turn-complete":
        return 0

    # Always play a sound when an agent turn completes
    try:
        play_sound()
    except Exception:
        pass

    # Always show a notification for agent-turn-complete (user request)
    try:
        title, msg, subtitle = build_notification_text(event)
        show_notification(title, msg, subtitle)
    except Exception:
        pass

    return 0


if __name__ == "__main__":
    raise SystemExit(main())

How to use this script:

Below is a brief description and testing suggestions:
- Main modification: Now, whenever event.type == "agent-turn-complete" is received, the script will simultaneously play a sound and pop up a system notification (regardless of whether it is the last turn). If the event is heuristically judged as "conversation finished," the notification title/content will show "Conversation finished"; otherwise, it will show "Agent turn complete" and display a brief excerpt.
- Testing method: Use your previous test_notifier.py (or the test script I provided before) to run the following command:
    - Assuming saved as notifier.py and test_notifier.py:
        - python3 test_notifier.py ./notify.py

- Observations:
    - agent-turn-complete-not-end (e.g., {"type":"agent-turn-complete","turn":1,"total_turns":5}) — should play sound and pop up a brief notification.
    - Events with is_last_turn or payload.conversation_finished — should play sound and pop up a "Conversation finished" notification (including summary).

- If notifications are not seen on some platforms:
    - macOS: Run osascript -e 'display notification "test" with title "Test"' in terminal to test; afplay/say/osascript are usually available by default.
    - Linux: Ensure notify-send (libnotify) or python-gi is installed.
    - Windows: Confirm running in desktop session, PowerShell available; bubbles may be blocked under remote desktop or some security policies.

Here is the test script:

#!/usr/bin/env python3
"""
Test harness for the notifier script.

This script runs several sample events against the notifier and prints the
notifier's exit code and any stdout/stderr. It pauses briefly between tests
so sounds/notifications can be seen/heard.

Usage:
    python3 test_notifier.py [path_to_notifier_script]

If path_to_notifier_script is omitted, ./notifier.py is used by default.
"""

import json
import os
import subprocess
import sys
import time


def run_test(name, event, notifier_path):
    """Run a single test: call notifier with JSON argument and print results."""
    print(f"\n--- Test: {name} ---")
    arg = json.dumps(event)
    print("Event JSON:", arg)
    try:
        completed = subprocess.run(
            [sys.executable, notifier_path, arg],
            capture_output=True,
            text=True,
            timeout=20,
        )
        print("Return code:", completed.returncode)
        if completed.stdout:
            print("Stdout:", completed.stdout.strip())
        if completed.stderr:
            print("Stderr:", completed.stderr.strip())
    except subprocess.TimeoutExpired:
        print("Error: notifier timed out")
    except FileNotFoundError:
        print(f"Error: notifier script not found at {notifier_path}")
    except Exception as e:
        print("Error running notifier:", repr(e))

    # Wait so the notification/sound has time to appear
    time.sleep(3)


def main():
    notifier_path = sys.argv[1] if len(sys.argv) > 1 else "./notifier.py"
    if not os.path.exists(notifier_path):
        print("Notifier script not found at", notifier_path)
        sys.exit(2)

    tests = [
        # 1) Non-agent event: should do nothing
        ("non-agent-event", {"type": "other-event"}),
        # 2) Agent turn complete but not end: should at least play a sound
        ("agent-turn-complete-not-end", {"type": "agent-turn-complete", "turn": 1, "total_turns": 5}),
        # 3) Agent turn complete with explicit is_last_turn boolean (should notify)
        ("agent-turn-complete-is-last", {"type": "agent-turn-complete", "is_last_turn": True, "summary": "Final summary: conversation complete."}),
        # 4) Agent turn complete with nested payload flag
        ("agent-turn-complete-payload-flag", {"type": "agent-turn-complete", "payload": {"conversation_finished": True, "summary": "Finished via payload flag"}}),
        # 5) Agent turn complete with conversation_state string
        ("agent-turn-complete-state-finished", {"type": "agent-turn-complete", "conversation_state": "finished", "text": "Conversation reached finished state"}),
    ]

    print("Using notifier at:", notifier_path)
    print("Starting tests. You should hear sounds and (for end events) see system notifications if your platform supports them.\n")

    for name, event in tests:
        run_test(name, event, notifier_path)

    print("\nAll tests completed.")


if __name__ == "__main__":
    main()
nialse · 7 months ago
@Ahmet-Dedeler Just ask the Codex to do a terminal beep after a task.

On a Mac that would be the same as setting ~/.codex/config.toml:

notify = ["tput", "bel"]

Unfortunately it does not sound the bel(l) on permission requests though.

huangwb8 · 7 months ago

I developed a python package to do this. For MacOS/Linux/Windows; For Claude Code/Codex. More details in [huangwb8/VibeNotification](https://github.com/huangwb8/VibeNotification )

jiananlu · 7 months ago

on a mac:

$ brew install terminal-notifier

In codex config.toml, add this:

notify = ["bash", "-lc", "echo 'Codex Needs Your Attention' | terminal-notifier -sound default"]
PkmX · 7 months ago
> @Ahmet-Dedeler Just ask the Codex to do a terminal beep after a task. On a Mac that would be the same as setting ~/.codex/config.toml: notify = ["tput", "bel"] Unfortunately it does not sound the bel(l) on permission requests though.

I find that on Linux, tput bel does not accept the additional parameter that is passed down by codex and will fail silently.

A more portable solution on POSIX is:

notify = ["printf", "\\a%.0s"]

The %.0s conversion will shallow up the additional argument.

wybert · 6 months ago
The following worked for me on macOS, simple and easy to maintain, without the hassle of creating a Python script (notify.py) or a Shortcut. In ~/.codex/config.toml, add this line: model = "gpt-5-codex" model_reasoning_effort = "low" + notify = ["bash", "-lc", "afplay /System/Library/Sounds/Bottle.aiff"] /System/Library/Sounds has many different ringtones for you to choose. You don't have to use my choice. `` /System/Library/Sounds ├── Basso.aiff ├── Blow.aiff ├── Bottle.aiff ├── Frog.aiff ├── Funk.aiff ├── Glass.aiff ├── Hero.aiff ├── Morse.aiff ├── Ping.aiff ├── Pop.aiff ├── Purr.aiff ├── Sosumi.aiff ├── Submarine.aiff └── Tink.aiff `` (Inspired by #4998 (comment))

It raises error › /Users/xx/.bash_profile: line 38: syntax error: unexpected end of file after it played the sound

evan2306 · 6 months ago

Hey everyone, I’ve been using Codex since last September, and I just can't understand why it requires users to write their own script for the task completion notifications feature.
Is that really a good user experience? I don't think so. Even Google’s new Antigravity feature does this better than Codex.

I’m not really sure how difficult this is to implement. Can someone explain it to me? (Genuinely asking)

regenrek · 6 months ago

For anyone that struggles with sound integration - codex-1up comes with sound options. (Not a fork)

https://github.com/regenrek/codex-1up

fahedslx · 6 months ago

I already implemented this a while ago, but it got closed. My mistake I didn't check on these issues first. I just forked and did it for my own use but it can be an starting point.

It sounds a beep when task id done or user input is required, and has a simple menu option to enable/disable. I have been using this daily with proper behavior on ubuntu 24.04:

https://github.com/openai/codex/pull/8417#issuecomment-3680900135

BowTiedCrocodile · 6 months ago

I made a Mac native app that fire sounds and a native notification badge when Codex finishes its turn.

https://github.com/BowTiedCrocodile/codex-notifications-mac

paultendo · 6 months ago

I put together a macOS helper that solves the ‘quiet completion sound’ problem for me and I’ve found it genuinely handy. It sends native notifications (with optional VSCode activation) and lets you choose a louder sound via CODEX_NOTIFY_SOUND (e.g., Funk).

Repo + quick start: https://github.com/paultendo/codex-notify
Example for a loud sound:

Works well as a stopgap until this lands in Codex itself.

victornpb · 5 months ago

This Displays a notification, and says Finished! (MacOS Only)

config.toml

notify = [
  "bash",
  "-lc",
  "osascript -e 'display notification \"Task finished!\" with title \"Codex\"'; afplay /System/Library/Sounds/Glass.aiff; say -v 'Daniel' 'Finished!';",
]
victornpb · 5 months ago

(For Mac users)

Anyone looking an easily drop in customisable way, I made this notify.js helper.
It's ready for Notification, Sound, Text-to-speech notification, and Discord notifications.

~/.codex/config.toml
notify = ["/usr/bin/env", "node", "/Users/YOUR_USER_NAME/.codex/notify.js"]
~/.codex/notify.js
#!/usr/bin/env node
import { appendFileSync } from 'node:fs';
import { execFileSync } from 'node:child_process';
// helpers
const log = (...args) => appendFileSync(new URL('./notifications.log', import.meta.url), new Date().toLocaleString() + '\t' + args.map((x) => x !== null && typeof x === 'object' ? JSON.stringify(x) : x).join('\t') + '\n', { encoding: 'utf8' });
const truncate = (str)=>{const l=str.split(/\r?\n/),t=l.length>2?`(…) ${l.at(-2)}\n${l.at(-1)}`:str;return t.length<=100?t:`(…) ${t.slice(-130)}`;};
const escape = (str) => str.replace(/"/g, '\\"').replace(/\n/g, '\\n');
const notify = (title, message) => execFileSync('/usr/bin/osascript', ['-e', `display notification "${escape(message)}" with title "${escape(title)}"`], { stdio: 'ignore' });
const playSound = (name='Glass') => execFileSync('/bin/bash', ['-lc', `afplay /System/Library/Sounds/${name}.aiff`], { stdio: 'ignore' });
const speak = (message, rate, voice) => execFileSync('/usr/bin/say', [...(voice ? ['-v', voice] : []), ...(rate ? ['-r', rate] : []), message], { stdio: 'ignore' });
const sendDiscordMessage = (message, webhook) => webhook && fetch(webhook, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ content: String(message) }) }).catch(e => log('Discord error', e));

let payload = {};
try { payload = JSON.parse(process.argv[2]); } catch(O_o) { }
const status =  String(payload?.type).replaceAll('-', ' ').replaceAll('agent', 'Codex');
const lastMessage = String(payload?.['last-assistant-message']);
const lastSentence = lastMessage.split(/\r?\n/).at(-1);
log("Codex notification:", payload);

// Customise what you want below

playSound("Glass");

notify(status, truncate(lastMessage)); // Mac notification

speak(status, 300); // say Codex turn complete, Approval requested etc
// or
speak(truncate(lastSentence), 350); // say the last sentence

sendDiscordMessage(
`**${status}**
> ${lastSentence}`,
'https://DISCORD_WEBHOOK');

This way I can get a push notification on my phone tru a discord webhook if I walk away from my desk. I just mute the channel while im sitting on the pc.

etraut-openai contributor · 5 months ago

I wanted to provide an update on this feature request, which covers both the Codex CLI and VS Code Extension.

The CLI has long supported a configuration option named tui.notifications. See this documentation for details. When this is enabled, Codex sends a notification when finishing a prompt or asking for tool call approval or both. This notification has historically been delivered as an OSC 9 escape sequence, which some terminals support and turn into a desktop toast notification. Unfortunately, support for OSC 9 isn't universal. For example, the builtin-in Terminal app in macOS doesn't support it. So this wasn't a complete solution for all Codex CLI users.

We've addressed this by updating tui.notifications to support a more universal solution — a simple BEL character, which most terminals honor by emitting a simple audio tone. The next version of the CLI will include a new config option tui.notification_method that accepts the values auto, osc9 and bel. It defaults to auto, which attempts to automatically detect whether the terminal supports OSC 9 and falls back on BEL if it doesn't.

For the VS Code Extension, this feature request involves some complications. VS Code doesn't expose a standard way for extensions to emit desktop notifications. It _does_ provide a way for an extension to request a toast within VS Code, but this notification isn't very useful if you are not actively watching for it. It also doesn't support an audible signal. That means our options are rather limited here. If you have some thoughts about how such a notification feature might work best from within a VS Code extension, let us know.

victornpb · 5 months ago
I wanted to provide an update on this feature request, which covers both the Codex CLI and VS Code Extension. The CLI has long supported a configuration option named tui.notifications. See this documentation for details. When this is enabled, Codex sends a notification when finishing a prompt or asking for tool call approval or both. This notification has historically been delivered as an OSC 9 escape sequence, which some terminals support and turn into a desktop toast notification. Unfortunately, support for OSC 9 isn't universal. For example, the builtin-in Terminal app in macOS doesn't support it. So this wasn't a complete solution for all Codex CLI users. We've addressed this by updating tui.notifications to support a more universal solution — a simple BEL character, which most terminals honor by emitting a simple audio tone. The next version of the CLI will include a new config option tui.notification_method that accepts the values auto, osc9 and bel. It defaults to auto, which attempts to automatically detect whether the terminal supports OSC 9 and falls back on BEL if it doesn't. For the VS Code Extension, this feature request involves some complications. VS Code doesn't expose a standard way for extensions to emit desktop notifications. It _does_ provide a way for an extension to request a toast within VS Code, but this notification isn't very useful if you are not actively watching for it. It also doesn't support an audible signal. That means our options are rather limited here. If you have some thoughts about how such a notification feature might work best from within a VS Code extension, let us know.

For TUI current approach seems sufficient IMO. For the vscode extension most workarounds using the external command relies on invoking system notification directly like using osascript like on my previous message, which is fine as a work around but not as first party option as the notification comes from osa and not from vscode, clicking the notification brings up ScriptEditor instead of the correspondent vscode window.
I don't see any path forward other than requesting new APIs to the vscode team, this makes a compelling case for proposing these to be added to the extension APIs.

coreycoto · 5 months ago

Thanks for the update, @etraut-openai!

I would love to have different sound notifications for completions and user approval prompts in VS Code, and a little research with Devin.

-----

For the VS Code extension, the limitations are indeed architectural. Extensions currently only have access to internal toasts via vscode.window.showInformationMessage which require VS Code to have focus. There's no direct desktop notification or audio API exposed.

However, VS Code already has the necessary infrastructure internally:

  • IAccessibilitySignalService for playing sounds used by core features
  • ChatWindowNotifier demonstrates OS notification integration with proper click handling via hostService.showToast()

Proposed VS Code API additions:

namespace window {
   export function showDesktopNotification(
     message: string,
     options?: DesktopNotificationOptions
   ): Thenable<DesktopNotificationResult>;
 }
 
 namespace audio {
   export function playSound(soundId: string): void;
   export function registerSound(soundId: string, uri: Uri): Disposable;
 }

These would leverage existing IHostService and IAccessibilitySignalService infrastructure, with graceful degradation for web/remote scenarios.

The implementation would require:

  1. Adding RPC methods in extHost.protocol.ts
  2. Main thread handlers using existing services
  3. Permission model for notification access
  4. Starting in vscode.proposed.d.ts per extension proposal process

This approach reuses proven patterns while maintaining security boundaries.

coolbirdzik · 5 months ago

Simple:
Add to prompt to AGENTS.md

When you done task please play sound by command "powershell -NoProfile -Command (New-Object System.Media.SoundPlayer 'C:\\Windows\\Media\\Ring06.wav').PlaySync()" 

For Window IDE version:
Create a python script at "%USERPROFILE%\\.codex" or everywhere you want and paste it snippet (replace DEFAULT with your sound file):

#!/usr/bin/env python3
import sys
import os

DEFAULT = r"C:\Users\ngtan\Music\noti.wav"


def play_wav(path: str) -> None:
    path = os.fspath(path)
    if not os.path.isfile(path):
        raise FileNotFoundError(path)

    if sys.platform.startswith("win"):
        import subprocess

        # Spawn PowerShell and use System.Media.SoundPlayer PlaySync (blocks until finished).
        cmd = [
            "powershell",
            "-NoProfile",
            "-Command",
            f"(New-Object System.Media.SoundPlayer '{path}').PlaySync()",
        ]
        subprocess.run(cmd, check=True)
        return 0

    # Fallback - try playsound if available
    try:
        from playsound import playsound

        playsound(path)
        return 0
    except Exception:
        raise RuntimeError("No supported audio player available on this platform")


def main(argv: list[str] | None = None) -> int:
    argv = list(argv or sys.argv)
    path = argv[1] if len(argv) > 1 else DEFAULT

    try:
        play_wav(path)
    except FileNotFoundError:
        print(f"ERROR: file not found: {path}", file=sys.stderr)
        return 2
    except Exception as e:
        print(f"ERROR: failed to play '{path}': {e}", file=sys.stderr)
        return 3

    return 0


if __name__ == "__main__":
    raise SystemExit(main())

Then update config.toml (must replace C:\\Users\\ngtan\\.codex\\notify.py to your location of notify file):

notify = ["python3", "C:\\Users\\ngtan\\.codex\\notify.py"]

Sometimes you need to install Codex CLI
Enjoy.

kul-work · 4 months ago

Still not working in Windows env, CLI codex-cli 0.105.0-alpha.6

I'm testing this for weeks with no success. The trouble is that notify from c:\Users\<user>\.codex\config.toml

notify = ["powershell", "-NoProfile", "-Command", "[console]::beep(900,200)" ]

is not fired automatically when codex is running; the model can instead trigger it manually. tested also by logging to a file: as setting no luck, manually fired works.

My dirty workaround: just tell agent to make it for you (it's noise log, it's tokens consumer but it's working) - put this in top of your AGENTS.md

## Task notification

If you are not Amp, when you done task please play sound by command "powershell -NoProfile -Command [console]::beep(900,200); Start-Sleep -Milliseconds 120; [console]::beep(1200,250)"
etraut-openai contributor · 4 months ago

@kul-work, if you're using the CLI, I recommend using the tui.notification config key (and optionally the tui.notification_method key if you want to control whether to use an osc9 or bel notification). If you're having problems with this mechanism, please file a bug report. This feature request is still open because we don't have a good solution in place yet for the Codex IDE extension.

paultendo · 4 months ago

Following up on my earlier comment — codex-notify now covers a lot of the gaps discussed here, especially for macOS users on the CLI and the VS Code extension:

  • Different sounds for completion vs approval/input-needed (Glass vs Sosumi by default, fully configurable)
  • Click activates the right app — VSCode by default, and it auto-detects when the Codex macOS app is frontmost and redirects the click there instead
  • TTS via macOS say for when you're away from your desk
  • Webhooks (Discord, Slack, etc.) for remote notifications
  • DND/Focus awareness, schedule windows, rate limiting, notification log, custom hooks
  • One-line install and --setup for zero-friction config

Re: @etraut-openai's point about the VS Code extension lacking desktop notification APIs — the notify hook in config.toml already fires from the VS Code extension (since it shares codex-rs/core), so codex-notify works there too. The notification comes from terminal-notifier with execute-only activation, so clicking it brings VS Code to front reliably — no Script Editor redirect.

I've also opened PR #12516 to add a client field to the notify hook payload. This would let notification tools know whether the hook was triggered from the CLI, VS Code extension, or macOS app — useful for activating the correct application on click without having to guess.

tvjmohan · 4 months ago

But we can ask the codex in the cursor IDE , to play a sound once you are done with the task. it is working.

xiaotonng · 4 months ago

A slightly different angle on this — if you're stepping away and want to know when Codex finishes (and see what it did), I built an open-source bridge called pikiclaw that streams Codex CLI output to Telegram in real-time.

It runs locally and uses your own Telegram bot, so when Codex completes a task, the result shows up as a Telegram message on your phone — with native push notifications. You can also send follow-up prompts from the chat without going back to your terminal.

Not a replacement for a local sound notification (which the hook-based solutions above handle well), but useful if you're away from your desk and want more than just a "ding."

(Disclosure: I'm the author.)

kdawgwilk · 3 months ago

With the new hooks configuration this is now much easier to configure yourself 👍 https://developers.openai.com/codex/hooks

ShunmeiCho · 3 months ago

Relatable problem — long-running Codex tasks while working remotely.

I built a notification bridge into cc-clip that works over SSH. When Codex finishes a task, it forwards the notification through an SSH reverse tunnel to a local macOS daemon, which pops up a native notification.

For Codex specifically, it hooks into the \notify\ config in \~/.codex/config.toml\:

\\\toml
notify = ["cc-clip", "notify", "--from-codex-stdin"]
\
\\

\cc-clip connect\ auto-configures this if it detects \~/.codex/\ on the remote.

It's a third-party tool so not ideal compared to built-in support, but it works today for remote Codex sessions where \notify-send\ and terminal bell aren't available.

alexcstark · 3 months ago

+1 — would love a built-in notification/sound for turn completion in the CLI TUI.

simfor99 · 3 months ago

Adding another data point from Codex CLI on WSL2/WSLg, because the current workaround story is much rougher than it should be.

Environment tested on April 9, 2026:

  • Codex CLI 0.118.0
  • WSL2 / WSLg on Windows
  • terminal workflow, not just VS Code extension usage

What we found:

  • Claude has a much smoother built-in sound-notification experience.
  • Codex does expose notification-related config, but getting a reliable, pleasant completion sound in practice is still very hacky.
  • Terminal bell style approaches were effectively unusable here.
  • Hook-based workarounds technically worked, but UX was poor: silent in some paths, crackly/distorted in others, or unexpectedly too loud.
  • Even when we switched playback backends, the result was not something we would want to leave enabled during normal work.

Request:
Please add first-party, optional completion sounds for Codex CLI itself, with Claude-level ergonomics.

What would help most:

  • a simple on/off config flag for turn-complete sound
  • at least one curated default sound that is intentionally mild and volume-safe
  • consistent behavior across Linux, macOS, Windows, and WSL/WSLg
  • support for both "turn finished" and "needs user attention / approval"
  • ideally a tiny preview/test command so users can validate the sound before enabling it

The main point is not that custom scripting is impossible. It is that the current DIY route is too fragile and too easy to get wrong in a way that produces either no signal or an unpleasant one.

simfor99 · 3 months ago

One follow-up, because I want to be fair to the newer hook-based comments while also being precise about what is and is not solved.

The newer hooks configuration does improve the situation in one important way: it makes wiring a completion action into Codex much more straightforward than older ad-hoc workarounds.

However, that is not the same thing as this feature being genuinely solved from a user-experience perspective.

In our April 9, 2026 test on Codex CLI 0.118.0 under WSL2/WSLg:

  • the hook triggering itself worked
  • but the actual audio experience was still not acceptable in practice
  • depending on backend/approach, the result was silent, crackly/distorted, or unexpectedly too loud

So I think the accurate framing is:

  • hooks are a real improvement for custom integration
  • but they do not yet replace a first-party, curated, cross-platform completion sound experience

That distinction matters, because otherwise it sounds like users already have a good solution when in practice many of us still do not.

etraut-openai contributor · 3 months ago

@simfor99, this thread is for the Codex IDE extension. It sounds like you're having issues with the Codex CLI, which uses a different notification mechanism that works within the constraints of terminals. If you're having issues with the Codex CLI notification mechanism, please open a separate bug report so the feedback doesn't get lost.

simfor99 · 3 months ago

Thanks for the clarification, @etraut-openai. You are right that my issue is about Codex CLI notification behavior rather than the IDE extension. I will move the discussion over to the CLI bug side so it does not get lost and continue there instead. Appreciate the pointer.

simfor99 · 3 months ago

Follow-up: I opened a separate CLI tracking issue here as suggested: #17235. Thanks again for pointing me to the correct surface.

cirosantilli · 2 months ago

I managed to get the hooks https://developers.openai.com/codex/hooks to work on codex-cli 0.125.0 Ubuntu 25.10 with:

~/.codex/config.toml

[features]
codex_hooks = true

[[hooks.Stop]]
[[hooks.Stop.hooks]]
type = "command"
command = 'spd-say "done"'
apws · 2 months ago

hi guys, 8hrs ago I did my first project and as a second I did tiny workaround for this, detecting pet visual state changes and firing sound clip, for now as win32 systray thing, it expects pet docked in bottom-left screen corner, hopefully DPI-aware, tested on 1920x1200 at 100% now ... its ugly but simple and it works ... https://github.com/apws/260504-codex-pet-screen-sounds
(macosx version will be added soon...)

constansino · 2 months ago

I published a small hook-based workaround for macOS users who want a clearer "Codex needs attention" signal, not just turn-complete notifications:

https://github.com/constansino/codex-attention-notifier

It sends a native macOS notification for PermissionRequest hook events. It does not approve/deny anything; it only notifies and logs locally.

I also wrote a short discussion post with usage details here:

https://github.com/openai/codex/discussions/21354

PhilChen-6765 · 2 months ago

I’d like to add a +1 for the Codex Desktop app specifically.

The current completion notification sound is easy to miss, especially when Codex is running in the background and I’m focused on another app. It would be helpful to have one or more louder notification sound options for turn completion / input-required events.

A few possible options:

  • Provide a louder built-in completion sound
  • Let users choose between several notification sounds
  • Add a volume/intensity setting for Codex app notifications
  • Optionally support custom local sound files on macOS/Windows

This would make long-running agent workflows easier to monitor without constantly watching the Codex window.

etraut-openai contributor · 2 months ago

@PhilChen-6765, this feature is already supported in the Codex app. This thread is specifically for the Codex IDE Extension.

PhilChen-6765 · 2 months ago

@etraut-openai Thanks for clarifying. I may not have explained it clearly.

I’m using Codex app version 26.506.31421 (2620). I’m aware that completion notifications are already supported in the Codex app. My request is not about adding notification support from scratch, but about the current notification sound being too quiet/easy to miss.

What I’m asking for is a louder or more noticeable sound option for the existing Codex app notifications, or a setting that lets users choose a louder/custom notification sound.

etraut-openai contributor · 2 months ago

@PhilChen-6765, the desktop app uses standard desktop notifications that are under the control of the OS. In any case, this thread is specific to the IDE Extension, so this isn't the right place to be discussing feature requests for the app.

AminDhouib · 2 months ago

For anyone here using custom notify.py scripts or one-off afplay commands, we built a small cross-platform notifier for Codex and other AI coding agents: https://github.com/DevinoSolutions/ai-agent-notifier

Just run npx ai-agent-notifier setup, reset your current sessions, and you are good to go!

---

https://github.com/user-attachments/assets/5714b528-7e04-478e-abfd-2a3d05db562c

Watch on YouTube

apws · 1 month ago

hi, glad to see there 2 nice "hooks"-related solutions for (also) sound notifications; I was totally new to codex 3 weeks ago, so only now understanding the architecture (btw, is codex-CLI used under the hood of codex-APP ?), but as I understood reading related issues here, "hooks" are still in development to be in parity to claude code, but this is probably something I didn't knew - sure; so I as a one of first codex test projects implemented "out of the box sound notifier" based on visual screen watching of PET badge parked into left-bottom screen corner (tested on win/mac, hidpi aware etc); sure IT IS hack, but is reliable and is fast/immediate - system notifications on windows are always quite weird and not instant - thanks for the new workarounds/hooks here too;
(this was hidden originally as off-topic, but it isn't, excuse me, I am writing here mostly because "system notifications" at least on windows is something which didn't worked for me well, so i was immediately forced to TRY something simple the "other way"...)
https://github.com/apws/260504-codex-pet-screen-sounds

alsotang · 1 month ago

any update here? i dont need the sound. i need visual notification.

pal03377 · 28 days ago

Someone built an additional plugin that can do this (but it does not work via the Remote SSH extension :()
https://marketplace.visualstudio.com/items?itemName=zis3c.codex-notifier

finebyme99 · 9 days ago

Please also support custom notification sounds in the Codex desktop app.

Suggested settings:

  • Choose from built-in sounds
  • Import a local WAV/MP3 file
  • Preview the selected sound
  • Configure separate sounds for task completion, user input required, approval required, and failure
  • Restore the default sound easily

Currently, the macOS app bundles a fixed notification sound. Replacing that file manually invalidates the app code signature and may prevent the app from launching, so this needs an officially supported setting.

This would be especially useful for long-running tasks and for users who work with several agent applications simultaneously.