Skip to main content

ChatGPT Desktop Updates mit /tmp noexec unter Fedora

Einleitung

Ich benutze aktuell viel Codex (den Agenten von ChatGPT) und das natürlich auf meinem Fedora 44 System. Problem nur: Es gab bis vor einigen Wochen gar keinen Linux Client für Codex. Mittlerweile gibt es einen, aber wieder mal nur ein .deb ... so weit so obvious.

Glücklicherweise hat die Community das Problem mal wieder gelöst und einen Installer erstellt, der den Codex-Desktop auch auf andere Linux Distros bringt. Vor dem Linux Client haben sie das macOS .DMG Paket als Grundlage benutzt und daraus ein Linux Paket gebaut. Und mittlerweile bauen sie einfach das Linux Paket entsprechend um.

Und dann kommt Fedora :D

Auf Fedora 44 ist /tmp schon im Standard als eigenes tmpfs mit noexec eingehängt. Dafür muss man das System nicht einmal extra härten. Das ist eine sinnvolle Sicherheitsmaßnahme: Dateien dürfen dort abgelegt, aber nicht direkt als Programme gestartet werden. Das war beim bisherigen Aufbau mit macOS Wrapper ein echtes Problem.

Der Linux-Wrapper codex-desktop-linux hat seine Paketquelle inzwischen glücklicherweise geändert. Seit Version 0.11.x wird keine macOS-DMG mehr konvertiert. Stattdessen dient OpenAIs signiertes offizielles Linux-Paket als Quelle. noexec /tmp bleibt dennoch relevant, weil die strukturelle Prüfung des extrahierten ELF-Payloads Ausführbarkeit voraussetzt.

Dieser Artikel beschreibt zuerst die aktuelle, geprüfte Methodik mit automatischem exec-fähigem Build-Verzeichnis. Die frühere DMG-/Node.js-Lösung bleibt darunter als historischer Abschnitt erhalten, damit ältere Installationen und Logs weiterhin nachvollziehbar sind. /tmp bleibt bei allen beschriebenen Lösungen unverändert auf noexec (natürlich!).

Stand: 19. August 2026

Aktuelle Methodik: signiertes offizielles Linux-Paket

Der aktuelle Wrapper lädt die stabile APT-Metadatenkette von OpenAI und baut daraus weiterhin ein getrennt gekennzeichnetes Community-Paket. Paketname und Kommando bleiben codex-desktop, der Installationspfad bleibt /opt/codex-desktop, und der Desktop-Eintrag heißt ChatGPT Community.

Die Vertrauenskette ist dabei enger als bei der früheren DMG-Konvertierung:

  1. InRelease des stabilen OpenAI-APT-Repositorys wird mit dem fest hinterlegten Repository-Schlüssel geprüft.
  2. Größe und SHA-256 der passenden Packages-Datei werden validiert.
  3. Es wird genau ein Paket chatgpt für amd64 oder arm64 ausgewählt; Paketname, Architektur, Metadaten, Größe und SHA-256 werden geprüft.
  4. dpkg-deb -x extrahiert ausschließlich den Datenpayload. Upstream-Maintainer-Skripte werden nicht ausgeführt.
  5. Ohne aktivierte Linux-Zusatzfeatures bleibt resources/app.asar bytegleich zum offiziellen Paket.

Neue Ausprägung des noexec-Problems

Der aktuelle Installer legte sein Arbeitsverzeichnis zunächst weiterhin mit mktemp -d unter /tmp an. Der offizielle ELF-Payload wird beim Paketbau nicht gestartet. Die strukturelle Prüfung verwendet jedoch test -x für /usr/lib/chatgpt/ChatGPT und weitere ausführbare Dateien. Auf einem noexec-Mount liefert diese Prüfung trotz Dateimodus 0755 den Wert false; ein direkter Start endet mit Status 126.

Dadurch erschien die irreführende Meldung:

Official package payload is missing executable /usr/lib/chatgpt/ChatGPT

Eine identische Testdatei lieferte unter ~/.cache/codex-desktop/build-tmp dagegen test -x=true und Exit-Status 0. Also ist wiedermal nicht der offizielle Payload defekt, sondern nur sein temporärer Ausführungskontext ungeeignet.

Automatischer exec-fähiger Build-Pfad

Der portierte Fix prüft den tatsächlichen Ausführungskontext mit einer kurzlebigen Probe und entscheidet direkt in install.sh:

  • Ein brauchbares geerbtes TMPDIR wird unverändert verwendet.
  • CODEX_BUILD_TMPDIR kann einen anderen Pfad explizit vorgeben; ein unbrauchbarer Override bricht früh mit einer klaren Meldung ab.
  • Bei noexec wird automatisch ${XDG_CACHE_HOME:-$HOME/.cache}/codex-desktop/build-tmp angelegt und mit Modus 0700 verwendet.
  • Das gewählte TMPDIR wird an alle Kindprozesse exportiert und das eigentliche Arbeitsverzeichnis gezielt dort erzeugt.

Der Fix ist Bestandteil des minimalen Update-Builders im RPM. Damit verwenden manuelle Builds, der dauerhaft laufende Update-Daemon und direkte Aufrufe von codex-update-manager check-now dieselbe Logik. Der frühere systemd-User-Drop-in ist nicht mehr erforderlich.

Geprüfter Migrationsstand vom 19. August 2026

Komponente

Geprüfter Wert

Wrapper

0.11.1, Basiscommit 1875dc2eaab6f9448617d18d0eda58902edf0f1a

lokaler noexec-Fix

f44667a7910c2ab82e4d37257154f6e28d324a0b

offizielles Linux-Paket

chatgpt 26.814.41957 amd64

offizieller Paket-Hash

4778b26a7abd08647214d5b05c17bd3ebe2d9688d146dabf017c1a2faf93ac7d

app.asar

keine Features aktiv; SHA-256 1a43bb2a6547cd2a4945a669fb14f0b15b6eddc1fc1177f51dffc554e3c5ad98, bytegleich erhalten

installiertes RPM

codex-desktop-2026.08.19.190339-1.fc44.x86_64

RPM-Hash

db4fbc33ef82ce96a651e5379ffe1c13171bac496c9efc83e4ac329d6fe4b682

Bestanden haben 39 Trust-/Patch-/Feature-Basistests, 51 von 51 Smoke-Tests, ein vollständiger Build mit absichtlich gesetztem TMPDIR=/tmp, die Runtime-Diagnose, die RPM-Digestprüfung und rpm -V. Anschließend hat der neue Update-Manager selbst die signierten Metadaten geladen, den offiziellen Payload geprüft, ein weiteres RPM gebaut und dieses installiert. Ein zweiter Check ohne TMPDIR und ohne systemd-Drop-in endete mit idle, ohne Kandidat und ohne Fehler.

Den bisherige Drop-in habe ich als 10-executable-build-tmp.conf.retired-20260819 recoverbar aufbewahrt. Der Dienst läuft nach daemon-reload ohne diesen Override. Vor der Migration wurde außerdem das letzte funktionierende RPM mit SHA-256 57202821e2b6aaa53ee1666e766a04d3f41c1c2f57ab4a2c952d0d5133d30de9 als Rückfallmöglichkeit gesichert.

Upstream-Nachweis und Code

Die kontrollierte Migration folgt Issue #1371. Die Reparaturen für die offiziellen Pakete 26.814.41407 und 26.814.41957 wurden mit PR #1373 und PR #1376 in main aufgenommen.

Die lokalen Anpassungen, Prüfdaten und der exakt erzeugte Patch liegen im privaten Forgejo-Repository codex-projekte/chatgpt-work-linux, Commit c49db7f. Besonders relevant sind die Migrationsdokumentation und der Patch für die offizielle Linux-Methode. Das Repository liegt auf meinem privaten git Server (Anleitung hier im Wiki).

Ausgangsumgebung der früheren DMG-Methode

Komponente

Verwendete Umgebung

Betriebssystem

Fedora Linux 44

temporäres Dateisystem

/tmp

 als 

tmpfs

 mit 

nosuid,nodev,noexec

Anwendung

ChatGPT Desktop über 

ilysenko/codex-desktop-linux

Update-Dienst

codex-update-manager.service

 als systemd-Userdienst

alternativer Build-Pfad

~/.cache/codex-desktop/build-tmp

Historisches Fehlerbild der DMG-Methode

Der Desktop meldet bei einem gefundenen Update zunächst, dass aus dem neuen Upstream-DMG ein lokales Linux-Paket vorbereitet wird. Kurz darauf folgt nur noch:

The local rebuild failed: install.sh failed during local rebuild

Im eigentlichen Build-Log steht beispielsweise:

Managed Node.js runtime copied from /opt/codex-desktop/update-builder/node-runtime
Managed Node.js runtime is missing node: /tmp/tmp.86Yk8CgVpa/node-runtime/bin/node

Das Log des Update-Dienstes liegt normalerweise hier:

tail -n 100 ~/.local/state/codex-update-manager/service.log

Die Logs des einzelnen Build-Versuchs liegen unter:

~/.cache/codex-update-manager/workspaces/<VERSION>/logs/install.log

Historische technische Ursache

Der Installer erzeugt sein Arbeitsverzeichnis ursprünglich mit einem einfachen:

WORK_DIR="$(mktemp -d)"

Ist kein TMPDIR gesetzt, verwendet mktemp den systemweiten Pfad /tmp. Der Installer kopiert anschließend den mitgelieferten Node.js-Runtime nach ${WORK_DIR}/node-runtime und startet ihn während des Builds mehrfach.

Die normalen Unix-Dateirechte können dabei korrekt aussehen. Das noexec-Flag des Mounts verhindert die Ausführung trotzdem. Die folgende Meldung spricht deshalb von einer fehlenden Node-Datei, obwohl eigentlich der Mount die Ausführung verweigert.

Der in der Projektdokumentation beschriebene manuelle Export von TMPDIR hilft für einen Build in derselben Shell. Ein systemd-Userdienst erbt diese interaktive Shell-Umgebung jedoch nicht zuverlässig. Zusätzlich startet der Paket-Launcher die Prüfung über eine transiente Unit namens codex-update-manager-launch-check.service. Die In-App-Update-Bridge kann codex-update-manager check-now sogar direkt aus dem Desktop-Prozess aufrufen. Ein Drop-in ausschließlich für codex-update-manager.service erreicht diese beiden Pfade nicht.

Historische Updater-Pfade

In der aktuellen Paketarchitektur sind mindestens drei Ausführungskontexte relevant:

  1. der dauerhafte Userdienst codex-update-manager.service,
  2. die beim Programmstart mit systemd-run --user erzeugte Unit codex-update-manager-launch-check.service,
  3. der direkte Aufruf durch die In-App-Update-Bridge.

Die beiden letzten Varianten erklären, warum der Fehler nach einem Neustart trotz korrekt geladenem Service-Drop-in erneut auftreten kann. Die robuste Lösung muss daher im Installer selbst liegen: Er prüft sein Arbeitsverzeichnis vor jedem Build auf tatsächliche Ausführbarkeit und wählt bei Bedarf selbstständig den privaten Cache-Pfad.

Historischen Mount- und Dienststand prüfen

Die Mount-Optionen von /tmp zeigt findmnt übersichtlich an:

findmnt -T /tmp -no TARGET,FSTYPE,OPTIONS

Eine passende Ausgabe sieht beispielsweise so aus:

/tmp tmpfs rw,nosuid,nodev,noexec,seclabel,size=...

Den aktuellen Zustand des Updaters zeigt:

systemctl --user show codex-update-manager.service \
  -p ActiveState -p SubState -p Environment -p DropInPaths

Historischen Build-Pfad vorbereiten

Das alternative Verzeichnis liegt bewusst im benutzerspezifischen Cache und bekommt den Modus 0700:

BUILD_TMP="${XDG_CACHE_HOME:-$HOME/.cache}/codex-desktop/build-tmp"

install -d -m 0700 "$BUILD_TMP"
stat -c '%A %a %U:%G %n' "$BUILD_TMP"

Erwartet wird eine Ausgabe mit drwx------ beziehungsweise Modus 700.

Vor der weiteren Einrichtung kann man zusätzlich prüfen, ob auf diesem Dateisystem wirklich Programme gestartet werden dürfen:

probe="$(mktemp "$BUILD_TMP/.codex-exec-probe.XXXXXX")"
printf '%s\n' '#!/bin/sh' 'exit 0' > "$probe"
chmod 0700 "$probe"

"$probe" && echo 'Ausführung möglich'
rm -f "$probe"

Historischer systemd-Teil-Workaround

Das folgende Drop-in ist weiterhin als Diagnose und Teil-Workaround sinnvoll. Es gilt jedoch ausschließlich für den dauerhaft laufenden Updater-Dienst. Dazu sind keine Root-Rechte erforderlich:

BUILD_TMP="${XDG_CACHE_HOME:-$HOME/.cache}/codex-desktop/build-tmp"
DROPIN_DIR="$HOME/.config/systemd/user/codex-update-manager.service.d"

install -d -m 0700 "$BUILD_TMP"
mkdir -p "$DROPIN_DIR"

printf '[Service]\nEnvironment="TMPDIR=%s"\n' "$BUILD_TMP" \
  > "$DROPIN_DIR/10-executable-build-tmp.conf"
chmod 0644 "$DROPIN_DIR/10-executable-build-tmp.conf"

systemctl --user daemon-reload
systemctl --user restart codex-update-manager.service

Der Dienst übernimmt die Einstellung bei jedem Start. Die transiente Startprüfung und direkte In-App-Aufrufe sind damit jedoch nicht abgedeckt. Der Drop-in allein ist daher kein vollständiger Auto-Update-Workaround.

Historische Lösung kontrollieren

Nach dem Neustart kann die neue Umgebung im dauerhaften Dienst sichtbar sein, ohne dass sie für alle Update-Prüfungen gilt:

systemctl --user show codex-update-manager.service \
  -p ActiveState -p SubState -p Environment -p DropInPaths

Die Teilkonfiguration enthält unter anderem:

ActiveState=active
SubState=running
Environment=TMPDIR=/home/<BENUTZER>/.cache/codex-desktop/build-tmp

Bei einem Build, der tatsächlich in diesem Dienst läuft, liegt das kurzlebige Arbeitsverzeichnis dann beispielsweise hier:

~/.cache/codex-desktop/build-tmp/tmp.YxD9pAlGXz/

Bei einer späteren Kontrolle starteten neue Prüfungen dennoch wieder unter /tmp/tmp.…. Das Journal ordnete diese Läufe eindeutig der transienten Unit codex-update-manager-launch-check.service zu. Der dauerhafte Dienst besaß gleichzeitig das korrekte TMPDIR. Damit war die Begrenzung des Drop-ins reproduzierbar nachgewiesen.

systemctl --user show-environment | grep '^TMPDIR='
journalctl --user-unit codex-update-manager-launch-check.service -n 50 --no-pager

Fehlt TMPDIR in der systemd-Benutzerumgebung, startet die transiente Unit ohne diese Variable. Ein globales Setzen wäre möglich, wirkt aber auf sämtliche Userdienste und löst den direkten In-App-Pfad nicht zuverlässig. Deshalb bleibt der Installer-Fix die bevorzugte Lösung.

Historischen Workaround wieder entfernen

Das Drop-in lässt sich ohne Änderung an der Paketinstallation zurückbauen:

rm -f "$HOME/.config/systemd/user/codex-update-manager.service.d/10-executable-build-tmp.conf"
systemctl --user daemon-reload
systemctl --user try-restart codex-update-manager.service

Das Cache-Verzeichnis wird dabei absichtlich nicht automatisch gelöscht. Nach Abschluss aller Builds kann es bei Bedarf separat entfernt werden.

Warum /tmp nicht mit exec einhängen?

Ein Remount von /tmp mit exec würde zwar den Build ermöglichen, schwächt aber eine systemweite Sicherheitsvorgabe für eine lokale Anforderung einer einzelnen Anwendung ab. Der benutzerspezifische Pfad begrenzt die Änderung auf den Build-Prozess und behält die Fedora-Härtung bei.

Auch ein globaler TMPDIR-Export ist weiter gefasst als nötig. Das systemd-Drop-in wirkt nur auf den dauerhaften Update-Dienst und ist deshalb allein nicht ausreichend. Der angepasste Installer ist robuster, weil er unabhängig vom aufrufenden Prozess selbst einen sicheren Build-Pfad auswählt.

Historisches Skript und früherer Quellcode-Patch

Der idempotente Installer, ein Diagnose- und Rückbauskript sowie ein Patch für codex-desktop-linux liegen in meinem privaten Forgejo-Repository:

codex-projekte/chatgpt-work-linux

Das Repository ist derzeit privat. Der Artikel enthält deshalb alle für den Workaround notwendigen Schritte zusätzlich vollständig und eigenständig.

Der Quellcode-Patch erweitert den Installer um folgende Punkte:

  • explizites CODEX_BUILD_TMPDIR als Override,
  • tatsächliche Ausführungsprobe für ein geerbtes TMPDIR,
  • automatischer Fallback auf ~/.cache/codex-desktop/build-tmp,
  • Verzeichnisrechte 0700,
  • Export von TMPDIR an alle nachfolgenden Build-Werkzeuge,
  • verständliche Fehlermeldung, wenn kein ausführbares Arbeitsverzeichnis verfügbar ist.

Umsetzung der früheren DMG-Methode

Der korrigierte Quellstand wird erst durch den Paketwechsel im laufenden System wirksam. Das neue Paket ist inzwischen installiert; der mitgelieferte /opt/codex-desktop/update-builder/install.sh enthält die automatische Auswahl eines ausführbaren Build-Verzeichnisses.

  1. Erledigt: aktueller Upstream-Stand mit aktueller DMG und deaktivierten optionalen Features gebaut.
  2. Erledigt: Akzeptanzbericht, RPM-Digests und Update-Builder geprüft.
  3. Erledigt: neues RPM über dnf installiert.
  4. Erledigt: exakter transienter Launch-Check ohne TMPDIR mit Status 0/SUCCESS ausgeführt; dabei entstand kein neuer Workspace.
  5. Erledigt: Desktop-App vollständig neu gestartet; keine Fehlermeldung beim Start. Der automatische Launch-Check um 12:29 Uhr endete mit Result=success und ExecMainStatus=0.
rpm -qip ./dist/codex-desktop-*.rpm
rpm -qlp ./dist/codex-desktop-*.rpm | grep '/opt/codex-desktop/update-builder/install.sh'

# Die Anwendung vor diesem Schritt beenden:
sudo dnf install ./dist/codex-desktop-*.rpm

grep -n 'configure_executable_build_tmpdir' \
  /opt/codex-desktop/update-builder/install.sh

Lokaler Stand vom 9. August 2026: Der vollständige Build gegen Upstream-DMG 26.803.41515 wurde ohne Integritätsfehler akzeptiert. Das RPM codex-desktop-2026.08.09.100802-f8a5a74a.noexec1.x86_64.rpm mit SHA-256 85c0eb42c0a76d1869d30cafa83d524dbfe3b55d439d72a0c4e4a10362ad181b ist installiert. Der Updater meldet idle ohne Fehler. Der exakte transiente Launch-Check lief ohne geerbtes TMPDIR erfolgreich und erzeugte keinen neuen Workspace. Nach dem vollständigen Neustart erschien keine Update-Fehlermeldung. Der automatische Launch-Check endete mit Result=success und ExecMainStatus=0; der Updater blieb idle und erzeugte keinen neuen Workspace. Damit ist der reguläre Autoupdate-Startpfad abschließend verifiziert.

Der lokale Paketwechsel ist der sofort verfügbare Weg. Parallel sollte der Fix upstream übernommen werden, damit künftige offizielle Pakete keinen zusätzlichen lokalen Patch benötigen.

Zuletzt aktualisiert am 19.08.2026 · 12 Aufrufe