Blog

Desktop-Benachrichtigungen von Codex unter Linux, Windows und WSL

Nov 20, 2025 11 min read development

Codex lässt sich über kleine Skripte mit nativen Desktop-Benachrichtigungen auf Linux, Windows und WSL verbinden.

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.

VS Code mit Codex-Panel und Codex-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:

  • type ist agent-turn-complete, wenn Codex eine Iteration abgeschlossen hat und wieder auf dich wartet.
  • last-assistant-message ist die letzte Antwort von Codex.
  • input-messages enthä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:

  1. Ein kleines Skript schreiben, das
    • das JSON aus argv[1] liest,
    • es parst,
    • entscheidet, ob benachrichtigt wird,
    • eine Desktop-Benachrichtigung auslöst.
  2. notify in der config.toml auf 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-send ist verfügbar (libnotify-bin unter Debian / Ubuntu).

Abhängigkeiten installieren

Unter Debian / Ubuntu:

sudo apt update
sudo apt install -y libnotify-bin python3

Notification-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.py

Codex-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.
  • notify muss 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.toml liegt in C:\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:

  • notify zeigt auf ein Bash-Skript in WSL.
  • Das Skript filtert das JSON und ruft powershell.exe auf.
  • 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 -Force

Falls 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 jq

Lege ~/.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 type nicht agent-turn-complete ist.
  • 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-BurntToastNotification direkt aus WSL, inkl. Icon (UNC-Pfad auf deine Distro/User anpassen oder -AppLogo entfernen).

Ausführbar machen:

chmod +x ~/.codex/notify.sh

Codex-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.toml muss diejenige sein, die Codex in WSL nutzt.
  • notify gehö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
Autor

Marco Kerwitz

Founder of kanman.de

Warum kanman

Scheiß auf Pläne. Scheiß auf Perfektion. kanman hält deine begonnenen Projekte im Fokus und lässt KPI- und Gamification-Kram weg.

  • Begonnene Projekte stehen immer vorne, ohne Dashboard-Overkill.
  • Priorisieren per Drag-and-drop – Aufgaben sortieren sich automatisch mit.
  • Keine Kalender, keine KPIs, keine KI, die dir sagt, was du tun sollst.
Kanman jetzt nutzen

Kein Abo, 49€ für lebenslangen Zugang.