Codex eine längere Refaktorierung oder einen mehrstufigen Change abarbeiten zu lassen, während du nebenbei etwas anderes machst, ist praktisch. Erst 20 Minuten später zu merken, dass Codex nach 30 Sekunden fertig war, eher nicht.
Diese Anleitung zeigt, wie du Codex mit nativen Desktop-Benachrichtigungen verbindest, und zwar auf:
- Linux / Unix (notify-send)
- Windows (Python + win10toast)
- WSL (Codex in WSL, Benachrichtigungen in Windows via PowerShell)
Alle drei Varianten nutzen denselben Mechanismus: Codex ruft pro abgeschlossenem Turn ein Script auf, übergibt ein JSON, und das Script feuert eine Desktop-Benachrichtigung.

Wo das gut in deinen Flow passt
Sobald Codex dich über das Betriebssystem benachrichtigt, funktionieren einige Workflows deutlich entspannter:
-
Lass Codex Änderungen quer durch die Codebase durchführen, während du parallel Merge Requests im Browser reviewst.
-
Lass Codex Integrationstests durchführen, Fehler beheben und dir anschließend „ready to review“ melden.
-
Kombiniere das Setup mit einem Todo-Manager wie Kanman, um größere Refaktorierungen sauber zu begleiten:
- Erstelle einen Kanman-Task wie „FooService auf neue API migrieren“.
- Lass Codex die Schritte abarbeiten.
- Jede erledigte Teilaufgabe triggert eine Benachrichtigung, du hakst die Punkte in Kanman ab.
Die Benachrichtigungs-Brücke löst ein ganz banales Problem: Du musst nicht dauernd nachsehen, ob Codex schon fertig ist. Stattdessen meldet er sich, wenn er dich wirklich wieder braucht.
Wie Codex-Benachrichtigungen funktionieren
Codex liest eine config.toml in ~/.codex (Linux / WSL) oder C:\Users\<user>\.codex (Windows).
Die Codex-Dokumentation zu Notifications erklärt Payload und notify, hört aber an der Schnittstelle auf. Ein Betriebssystem-spezifisches Skript musst du selbst liefern, und manche Umgebungen (WSL, sehr schlanke Linux-Desktops) können ohne Extras keine Benachrichtigungen anzeigen.
Es gibt genau einen Schlüssel, der zählt:
notify = ["command", "arg1", "arg2"]Codex ruft diesen Befehl auf, sobald ein unterstütztes Ereignis eintritt, und hängt als letztes Argument ein JSON mit Details zum Ereignis an.
Typische Payload:
{
"type": "agent-turn-complete",
"last-assistant-message": "I have finished applying the changes.",
"input-messages": [
"Refactor the FooService to use dependency injection.",
"Update all call sites accordingly."
]
}Wichtig:
typeistagent-turn-complete, wenn Codex eine Iteration abgeschlossen hat und wieder auf dich wartet.last-assistant-messageist die letzte Antwort von Codex.input-messagesenthält die Eingaben, auf deren Basis Codex den Turn ausgeführt hat.
In der Praxis wird input-messages oft mit IDE-Kontext aufgefüllt (z. B. offene Tabs, Arbeitsverzeichnis, aktive Datei). Außerdem sind die Antworten in last-assistant-message häufig als Markdown-Liste formatiert:
{
"type": "agent-turn-complete",
"thread-id": "019aa2d1-f501-7760-9354-afb6d88317e9",
"turn-id": "0",
"cwd": "/code/project",
"input-messages": [
"# Context from my IDE setup:\n\n## Active file: /home/<user>/.codex/config.toml\n\n## Open tabs:\n- config.toml: /home/<user>/.codex/config.toml\n- blog.html: themes/kanman/layouts/partials/sections/blog.html\n- hero.yaml: data/home/de/hero.yaml\n- blog.yaml: data/home/de/blog.yaml\n- styles.css: themes/kanman/assets/css/styles.css\n\n## My request for Codex:\nPlease revisit the product box in the sidebar of the blog post layout. Use the text content of the feature section and the hero section to come up with an actual meaningful pitch.\n"
],
"last-assistant-message": "- Refreshed the blog post sidebar pitch in `themes/kanman/layouts/_default/single.html` to mirror the hero/features messaging: project-first, no KPI/gamification noise, drag-and-drop Priorisierung, and no calendar/AI baggage.\n- Swapped CTA copy to “Kanman jetzt nutzen,” added a supporting bullet list with real strengths, and included the lifetime pricing subline from the hero text.\n\nTests not run (copy-only change). Next step: preview the blog post page to ensure the new sidebar layout/line breaks feel right."
}Ungefiltert wäre das eine überfrachtete und nutzlose Benachrichtung. Die Skripte unten machen deshalb drei Dinge:
- Sie extrahieren aus „My request for Codex“ einen kurzen, brauchbaren Titel.
- Sie übernehmen aus der Assistant-Antwort nur die Bullet-Points, falls vorhanden.
- Sie kürzen jede Zeile an Wortgrenzen, damit der Toast lesbar bleibt.
Du kannst darauf aufbauen, z.B.:
- den „Context from my IDE“-Block komplett entfernen, falls du keine Pfade leaken willst,
- Bullets behalten (praktisch, um mehrere erledigte Schritte zu sehen) oder zu einem Satz verdichten,
- verschiedene
type-Ereignisse unterschiedlich behandeln (z. B. Fehler vs. Abschluss).
Du musst im Kern nur:
- Ein kleines Skript schreiben, das
- das JSON aus
argv[1]liest, - es parst,
- entscheidet, ob benachrichtigt wird,
- eine Desktop-Benachrichtigung auslöst.
- das JSON aus
notifyin derconfig.tomlauf dieses Skript zeigen lassen.
Der Rest dieses Posts sind drei konkrete Minimalvarianten für die jeweiligen Umgebungen.
Anwendungsfälle: wann sich das lohnt
Das Setup lohnt sich immer dann, wenn Codex länger arbeitet und du nicht daneben sitzen willst:
- Große Refaktorierungen über viele Dateien hinweg.
- Code-Generierung mit anschließendem Testlauf und automatischen Fixes.
- Migrationen über Services oder Module.
- Alles, wo Codex zwischendurch um Freigaben bittet, bevor er Änderungen final anwendet.
Statt auf einen Fortschrittslog zu starren, kannst du derweil:
- in einem anderen IDE-Fenster arbeiten,
- Dokumentation schreiben,
- oder eben etwas ganz anderes tun.
Die Benachrichtigung holt dich dann zurück, wenn Codex fertig ist oder deine nächste Entscheidung braucht.
Variante 1: Linux / Unix mit notify-send
Annahmen:
- Du nutzt Codex in einer normalen Linux-Desktop-Session.
notify-sendist verfügbar (libnotify-binunter Debian / Ubuntu).
Abhängigkeiten installieren
Unter Debian / Ubuntu:
sudo apt update
sudo apt install -y libnotify-bin python3Notification-Script
Lege ~/.codex/notify.py an:
#!/usr/bin/env python3
import json
import re
import subprocess
import sys
def trunc(text: str, limit: int) -> str:
"""Truncate at a word boundary and add ellipsis when needed."""
if len(text) <= limit:
return text
cut = max(0, limit - 3)
head = text[:cut]
head = re.sub(r"\s+\S*$", "", head)
if not head:
head = text[:cut]
return f"{head}..."
def title_from_request(inputs: list[str]) -> str:
"""Use the 'My request for Codex' block as title, else fall back."""
block = "\n".join(inputs or [])
lines = block.splitlines()
capture = False
picked: list[str] = []
for line in lines:
if capture:
picked.append(line)
if line.startswith("## My request for Codex:"):
capture = True
picked.append(line.replace("## My request for Codex:", "", 1).strip())
title = " ".join(" ".join(picked).split()).strip()
return trunc(title or "Codex chat", 120)
def body_from_assistant(text: str) -> str:
"""Prefer Markdown bullets; otherwise condense to one line."""
bullets: list[str] = []
for line in text.splitlines():
if re.match(r"^\s*[-*]\s+", line):
bullets.append("- " + re.sub(r"^\s*[-*]\s+", "", line))
if bullets:
return "\n".join(trunc(b, 80) for b in bullets)
one_line = " ".join(text.split()).strip()
return trunc(one_line or "(no assistant message)", 220)
def main() -> int:
if len(sys.argv) < 2:
return 0
try:
payload = json.loads(sys.argv[1])
except json.JSONDecodeError:
return 0
if payload.get("type") != "agent-turn-complete":
return 0
assistant = (
payload.get("last-assistant-message")
or payload.get("last_assistant_message")
or ""
)
title = title_from_request(payload.get("input-messages") or [])
message = body_from_assistant(assistant)
try:
subprocess.run(["notify-send", title, message], check=False)
except Exception:
# Never break Codex on notification failure
pass
return 0
if __name__ == "__main__":
raise SystemExit(main())Das Skript:
- reagiert nur auf
agent-turn-complete, - baut den Titel aus „My request for Codex“ (Fallback: „Codex chat“) und kürzt ihn,
- übernimmt für den Body bevorzugt die Bullet-Points aus der Assistant-Antwort, sonst eine kondensierte Einzeile,
- feuert daraus eine Desktop-Benachrichtigung via
notify-send.
Ausführbar machen:
chmod +x ~/.codex/notify.pyCodex-Config unter Linux
Bearbeite ~/.codex/config.toml und stelle sicher, dass notify oben auf Root-Level steht:
model = "gpt-5.1-codex-max"
notify = ["python3", "/home/dein-user/.codex/notify.py"]
[history]
persistence = "save-all"
[tui]
notifications = ["agent-turn-complete"]Zwei Details:
- Nimm deinen echten Home-Pfad.
notifymuss auf Root-Level stehen, nicht in[tui]oder einer anderen Tabelle.
Schneller End-to-End-Test
Im Terminal:
python3 ~/.codex/notify.py \
'{"type":"agent-turn-complete","last-assistant-message":"Linux notification test","input-messages":["Example task"]}'Wenn ein Toast kommt, ist Codex angebunden. Gib Codex dann eine größere Aufgabe, wechsle weg und warte auf die Notification.
Variante 2: Windows mit win10toast
Unter Windows ist Python plus win10toast der schnellste Weg. Keine PowerShell-Module, kein Registry-Gedöns.
Voraussetzungen:
- Codex läuft direkt auf Windows, nicht in WSL.
config.tomlliegt inC:\Users\<user>\.codex\config.toml.
Python und win10toast installieren
Python installieren (falls nicht vorhanden), dann in PowerShell oder Terminal:
py -m pip install win10toast(oder python -m pip install win10toast, je nach Setup).
Notification-Script auf Windows
Datei anlegen:
C:\Users\<user>\.codex\notify.py
#!/usr/bin/env python
import json
import re
import sys
from win10toast import ToastNotifier
def trunc(text: str, limit: int) -> str:
"""Truncate at a word boundary and add ellipsis when needed."""
if len(text) <= limit:
return text
cut = max(0, limit - 3)
head = text[:cut]
head = re.sub(r"\s+\S*$", "", head)
if not head:
head = text[:cut]
return f"{head}..."
def title_from_request(inputs: list[str]) -> str:
"""Use the 'My request for Codex' block as title, else fall back."""
block = "\n".join(inputs or [])
lines = block.splitlines()
capture = False
picked: list[str] = []
for line in lines:
if capture:
picked.append(line)
if line.startswith("## My request for Codex:"):
capture = True
picked.append(line.replace("## My request for Codex:", "", 1).strip())
title = " ".join(" ".join(picked).split()).strip()
return trunc(title or "Codex chat", 120)
def body_from_assistant(text: str) -> str:
"""Prefer Markdown bullets; otherwise condense to one line."""
bullets: list[str] = []
for line in text.splitlines():
if re.match(r"^\s*[-*]\s+", line):
bullets.append("- " + re.sub(r"^\s*[-*]\s+", "", line))
if bullets:
return "\n".join(trunc(b, 80) for b in bullets)
one_line = " ".join(text.split()).strip()
return trunc(one_line or "(no assistant message)", 220)
def main() -> int:
if len(sys.argv) < 2:
return 0
try:
payload = json.loads(sys.argv[1])
except json.JSONDecodeError:
return 0
if payload.get("type") != "agent-turn-complete":
return 0
assistant = (
payload.get("last-assistant-message")
or payload.get("last_assistant_message")
or ""
)
title = title_from_request(payload.get("input-messages") or [])
message = body_from_assistant(assistant)
try:
toaster = ToastNotifier()
toaster.show_toast(
title,
message,
duration=5,
threaded=True,
)
except Exception:
pass
return 0
if __name__ == "__main__":
raise SystemExit(main())Gleiches Verhalten wie Linux/WSL:
- Reagiert nur auf
agent-turn-complete. - Titel aus „My request for Codex“, gekürzt; Fallback „Codex chat“.
- Body bevorzugt Markdown-Bullets; sonst eine kondensierte Zeile, jeweils gekürzt.
- Fehler werden geschluckt, damit Codex nicht stoppt.
Codex-Config auf Windows
Bearbeite C:\Users\<user>\.codex\config.toml:
model = "gpt-5.1-codex-max"
notify = ["py", "C:/Users/dein-user/.codex/notify.py"]
[history]
persistence = "save-all"
[tui]
notifications = ["agent-turn-complete"]Interpreter (py, python, python.exe) und Pfad anpassen.
Test
Aus PowerShell:
py C:/Users/dein-user/.codex/notify.py `
'{"type":"agent-turn-complete","last-assistant-message":"Windows notification test","input-messages":["Example task"]}'Wenn ein Windows-Toast erscheint, kann Codex jetzt pingen.
Variante 3: Codex in WSL, Benachrichtigungen unter Windows
Szenario:
- Codex läuft in WSL, weil dein Tooling dort zu Hause ist.
- WSL kann selbst keine Desktop-Benachrichtigungen anzeigen.
- Du willst trotzdem Windows-Notifications bekommen.
Die Lösung:
notifyzeigt auf ein Bash-Skript in WSL.- Das Skript filtert das JSON und ruft
powershell.exeauf. - PowerShell nutzt das BurntToast-Modul, um eine native Windows-Benachrichtigung zu erzeugen.
Kurz: Linux-Toolchain, Windows-Benachrichtigungen.
BurntToast auf Windows installieren
PowerShell:
Install-Module -Name BurntToast -Scope CurrentUser -ForceFalls NuGet fehlt, bei der Nachfrage installieren.
Schnelltest:
Import-Module BurntToast
New-BurntToastNotification -Text 'Codex', 'Direct BurntToast test'Bash-Wrapper in WSL (ruft PowerShell direkt)
Alles in einem Bash-Script – keine .ps1 auf Windows.
Installiere jq, falls fehlend:
sudo apt-get install -y jqLege ~/.codex/notify.sh an:
#!/usr/bin/env bash
set -euo pipefail
payload="${1:-}"
type=$(jq -r '.type // empty' <<<"$payload" 2>/dev/null)
[ "$type" != "agent-turn-complete" ] && exit 0
# truncate to max chars, cut back to last full word, add "..." if truncated
trunc() {
local s="$1" n="$2"
[ ${#s} -le $n ] && { printf '%s' "$s"; return; }
local cut=$((n-3))
local head
head=$(printf '%s' "${s:0:$cut}" | sed -E 's/[[:space:]]+[^[:space:]]*$//')
[ -z "$head" ] && head="${s:0:$cut}"
printf '%s...' "$head"
}
# ---- title: extract after "## My request for Codex:"
title=$(jq -r '."input-messages"[]? // ""' <<<"$payload" 2>/dev/null \
| awk 'f{print} /^## My request for Codex:/{f=1; sub(/^## My request for Codex:[[:space:]]*/,""); print}' \
| tr '\n' ' ' | sed -E 's/[[:space:]]+/ /g; s/^[[:space:]]+|[[:space:]]+$//g')
[ -z "$title" ] && title="Codex chat"
title=$(trunc "$title" 120)
# ---- body: bullets if present, else full assistant msg
assistant=$(jq -r '."last-assistant-message" // .last_assistant_message // ""' <<<"$payload" 2>/dev/null)
bullets=$(printf '%s\n' "$assistant" | grep -E '^[[:space:]]*[-*][[:space:]]+' || true)
if [ -n "$bullets" ]; then
message=$(printf '%s\n' "$bullets" \
| sed -E 's/^[[:space:]]*[-*][[:space:]]+/- /' \
| while IFS= read -r l; do trunc "$l" 80; echo; done)
else
one_line=$(printf '%s' "$assistant" | tr '\n' ' ' | sed -E 's/[[:space:]]+/ /g; s/^[[:space:]]+|[[:space:]]+$//g')
message=$(trunc "${one_line:-"(no assistant message)"}" 220)
fi
title_esc=${title//\'/\'\'}
message_esc=${message//\'/\'\'}
powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass \
-Command "Import-Module BurntToast; New-BurntToastNotification -Text '$title_esc', '$message_esc' -AppLogo '\\\\wsl.localhost\\Ubuntu-22.04\\home\\<dein-user>\\.codex\\codex-logo.png'"Was passiert hier:
- Bricht ab, wenn
typenichtagent-turn-completeist. - Holt den Titel aus „My request for Codex“, trimmt und kürzt auf 120 Zeichen (Fallback „Codex chat“).
- Nimmt Markdown-Bullets aus der Assistant-Antwort (je 80 Zeichen); sonst die Antwort auf 220 Zeichen kondensiert.
- Escaped einfache Anführungszeichen für PowerShell und ruft
New-BurntToastNotificationdirekt aus WSL, inkl. Icon (UNC-Pfad auf deine Distro/User anpassen oder-AppLogoentfernen).
Ausführbar machen:
chmod +x ~/.codex/notify.shCodex-Config in WSL
Bearbeite /home/dein-user/.codex/config.toml in WSL:
model = "gpt-5.1-codex-max"
notify = ["/bin/bash", "/home/dein-user/.codex/notify.sh"]
[history]
persistence = "save-all"
[tui]
notifications = ["agent-turn-complete"]Wichtig:
- Diese
config.tomlmuss diejenige sein, die Codex in WSL nutzt. notifygehört an die Wurzel, nicht in eine Tabelle.
Test aus WSL
~/.codex/notify.sh \
'{"type":"agent-turn-complete","last-assistant-message":"WSL test notification","input-messages":["Example task in WSL"]}'Wenn ein Windows-Toast erscheint, funktioniert die Brücke. Starte Codex in WSL, gib ihm einen längeren Job, wechsel weg, warte auf die Notification.
Optional: Icon nutzen
Zeige -AppLogo in ~/.codex/notify.sh auf eine Datei, die Windows lesen kann. Entweder UNC-Pfad nach WSL (\\\\wsl.localhost\\<distro>\\home\\<user>\\.codex\\codex-logo.png) oder ein Windows-Pfad (C:\\Users\\dein-user\\Pictures\\codex-logo.png). Wenn kein Icon gewünscht, Flag entfernen.
Zusammenfassung
Die Idee ist simpel:
- Codex kann nach jeder abgeschlossenen Iteration einen externen Befehl ausführen.
- Jedes Desktop-OS kann aus Skripten heraus Benachrichtigungen anzeigen.
- Mit ein paar Zeilen Python oder Bash kannst du beides sauber verbinden.
Linux: notify zeigt auf ein Python-Skript mit notify-send.
Windows: notify zeigt auf ein Python-Skript mit win10toast.
WSL: Ein Bash-Wrapper filtert das JSON und ruft BurntToast in Windows auf.
Damit kann Codex im Hintergrund arbeiten, während du dich um anderes kümmerst und nur dann zurückkommst, wenn wirklich etwas zu tun ist.
Marco Kerwitz
Founder of kanman.de