Skip to main content
Glama
ar36planet

claude-codex-bridge

by ar36planet

claude-codex-bridge

CI

Lass Claude Code mit dem Codex TUI vor dir sprechen – und du siehst alles. Umgekehrt kann Codex auch Nachrichten in die laufende Session von Claude Code schicken.

Kein Bildschirm-Scraping, kein Datei-Polling, kein headless Subagent. Beide hängen am selben Thread desselben codex app-server: Nachrichten, die Claude Code sendet, erscheinen sofort in dem TUI, das du gerade siehst.

English: README.en.md

Zum Installieren siehe: SETUP.md (中文) · SETUP.en.md (English)

Diese README beschreibt Designentscheidungen und Validierungsnachweise – warum dieser Ansatz, welche Fakten bereits gemessen wurden und welche nicht. Wenn du es direkt ausführen willst, ist SETUP schneller.

Validierungsumgebung: codex-cli 0.147.0, getestet unter Windows 11 und macOS 26, beide mit Node 24 LTS (Krypton). Der Code ist plattformunabhängig (Pfade immer über node:path, resolveCodex() hat nur einen Windows-Zweig als Sonderfall). Unterschiede zwischen den Plattformen und die jeweiligen Ergebnisse siehe unten unter „Validiert / Nicht validiert“.

Node-Anforderung ist >=22 (engines in package.json), empfohlen wird v24 LTS. Bei jedem Push wird eine vollständige Matrix aus ubuntu / macOS / Windows × Node 22, 24 in der CI ausgeführt.

codex ist oft ein globales Paket unter einer bestimmten nvm-Version, die niedriger als 22 sein kann – dann ist der standardmäßige node ebenfalls veraltet. Die sauberste Lösung ist, node und codex im selben LTS zu halten:

nvm install 24 && nvm alias default 24
nvm reinstall-packages 20        # 把 codex 等全域套件搬過去(20 換成你原本的版本)

(Der Binärpfad von codex ist ein Shim mit #!/usr/bin/env node, der mit dem node auf dem PATH läuft, nicht mit der Version bei der Installation.)

Architektur

        ┌──────────────────────────────┐
        │  codex app-server            │   ← 真正持有 thread 的地方
        │  --listen ws://127.0.0.1:8787│
        └───────┬──────────────┬───────┘
                │              │
   codex --remote ws://…       │  JSON-RPC over ws
                │              │
        ┌───────┴──────┐  ┌────┴─────────────┐
        │  Codex TUI   │  │  Claude Code     │
        │ (你在看)    │  │ (scripts/talk) │
        └──────┬───────┘  └────┬─────────────┘
               │               ▲
               └───────────────┘
        .bridge-inbox/<name>.jsonl → Stop hook
             (反方向:Codex → Claude Code)

Der Schlüssel in Vorwärtsrichtung liegt in der Semantik von thread/resume:

If thread_id identifies a running thread, app-server rejoins that thread.

Der zweite Client startet also keinen neuen Dialog und spielt kein Archiv erneut ab – er tritt demselben laufenden Thread bei. Erst nach dem Beitritt erhält er den Benachrichtigungsstrom dieses Threads; es reicht nicht, nur den Endpunkt zu verbinden.

Related MCP server: Claude-Gemini MCP Integration Server

Verwendung

Drei Fenster.

1. Gemeinsamer Server (offen lassen)

node scripts/serve.mjs --cwd C:\path\to\你的專案

--cwd ist das tatsächliche Arbeitsverzeichnis von Codex. Wenn der Thread kein eigenes cwd angibt, wird das des app-servers verwendet. Ohne diesen Parameter bleibt es in dem Verzeichnis, in dem das Startskript ausgeführt wird (also dem Bridge-Ordner). Alternativ kann die Umgebungsvariable CODEX_BRIDGE_CWD verwendet werden. Der Port wird mit --port oder CODEX_BRIDGE_PORT gesetzt (Standard 8787). Endpunkt und Workspace werden in .bridge.json geschrieben, talk.mjs liest sie selbst.

2. Das Codex TUI, das du sehen willst

codex --remote ws://127.0.0.1:8787 -C C:\path\to\你的專案

-C fixiert den Workspace dieses Fensters; ohne Angabe wird das oben mit --cwd gesetzte Verzeichnis verwendet.

Sprich zuerst eine Nachricht im TUI und warte, bis es antwortet. Der Thread muss eine erste Dialogrunde „durchlaufen“ haben, bevor er resumable ist. Vorher gibt thread/resume no rollout found for thread id zurück.

Achtung, diese Falle: talk.mjs list sieht den Thread bereits vorher – sobald das TUI verbunden ist, wird der Thread erstellt und erscheint in thread/loaded/list. „Liste zeigt ihn“ bedeutet also nicht „kann Nachrichten senden“. Überspringt man diesen Schritt, wird say gesendet, Codex antwortet auch im TUI, aber die Bridge empfängt den Antwortstrom nicht und wartet nur auf Timeout (unter macOS getestet).

3. Die Claude Code-Seite

node scripts/talk.mjs list               # 列出活著的 thread(含各自的 cwd)
node scripts/talk.mjs say "跑一下測試"     # 送話進去,你會在 TUI 看到
node scripts/talk.mjs read               # 讀完整 thread(結構化 JSON)

Bei nur einem Thread wählen say / read ihn automatisch aus; bei mehreren muss --thread <id> angegeben werden – es wird nicht geraten, mit welcher Session du sprichst. list gibt zusätzlich das cwd jedes Threads aus, zur Unterscheidung bei mehreren offenen Threads.

say akzeptiert außerdem --cwd <dir> (ändert nur das Arbeitsverzeichnis für diese Runde) und --approvals (siehe unten).

MCP-Schnittstelle

Das CLI ist weiterhin direkt nutzbar; die MCP-Schnittstelle bietet strukturierte Tools mit derselben Kernfunktionalität. Beide Richtungen teilen denselben Code, aber die aktive Sprechseite startet jeweils einen eigenen STDIO-Prozess:

  • --role claude: Claude Code spricht aktiv zum Codex-Thread.

  • --role codex: Codex stellt Nachrichten in das Claude-Postfach.

Installationsschritte

0. Voraussetzungen prüfen

  • Node >=22 (siehe Versionshinweise am Anfang).

  • Dieses Repo wurde mit npm install eingerichtet.

  • MCP ist nur die Schnittstelle, nicht die Transportschicht. Es benötigt weiterhin einen laufenden serve.mjs und ein verbundenes TUI, um etwas sagen zu können – siehe „Verwendung“ oben.

cd <這個 repo>
npm install

1. Entscheiden, welche Seite installiert werden soll

Gewünschte Funktion

Installation

Nur Claude Code kann zu Codex sprechen

Nur --role claude (Claude Code-Seite)

Nur Codex kann Nachrichten für Claude hinterlassen

Nur --role codex (Codex-Seite)

Bidirektional

Beide installieren

Wenn nur eine Richtung benötigt wird, installiere nicht beide. Die passive Empfangsseite nutzt kein MCP – sie läuft über app-server/TUI bzw. den Stop-Hook von Claude.

2. Installation

Claude Code-Seite (--role claude):

# macOS / Linux
claude mcp add --scope project claude-codex-bridge -- \
  node /path/to/claude-codex-bridge/scripts/mcp.mjs --role claude
# Windows
claude mcp add --scope project claude-codex-bridge -- `
  node "C:/path/to/claude-codex-bridge/scripts/mcp.mjs" --role claude

Codex-Seite (--role codex):

# macOS / Linux
codex mcp add claude-codex-bridge -- \
  node /path/to/claude-codex-bridge/scripts/mcp.mjs --role codex
# Windows
codex mcp add claude-codex-bridge -- `
  node "C:/path/to/claude-codex-bridge/scripts/mcp.mjs" --role codex

--scope project schreibt in die .mcp.json des Projekts; für projektübergreifende Nutzung durch --scope user ersetzen.

Verwende absolute Pfade, aber „in welchem Verzeichnis gestartet wird“ hat keine Auswirkung – alle Zustandsdateien (.bridge.json, .bridge-inbox/, .bridge-output/) werden relativ zum Modulpfad aufgelöst, nicht zum cwd. Eine Installation reicht also, kein separates Installieren pro Projekt nötig.

3. Neustart

claude mcp add / codex mcp add ändern nur die Konfigurationsdatei; bereits laufende Sessions laden den neuen MCP-Server nicht. Schließe die Session und starte sie neu, damit die Tools verfügbar sind.

4. Installation prüfen

Rufe in der neu gestarteten Session bridge_status auf. ok: true und korrekte role bedeuten Erfolg. Danach sollte codex_threads_list deinen TUI-Thread anzeigen (mit seinem cwd).

Ohne Session-Start kann derselbe Pfad auch direkt von der Kommandozeile geprüft werden:

npm run test:e2e:mcp-send        # 需要 serve + 已跑完第一輪的 TUI

Tools

Rolle

Tools

Gemeinsam

bridge_status, bridge_output_read

Claude

codex_threads_list, codex_thread_read, codex_message_send

Codex

claude_mailboxes_list, claude_mailbox_peek, claude_message_send

codex_message_send wartet auf den gesamten Turn, daher wird für die Codex-MCP-Konfiguration ein längerer Timeout empfohlen, und Schreib-Tools sollten eine Genehmigung erfordern:

[mcp_servers.claude-codex-bridge]
tool_timeout_sec = 360
default_tools_approval_mode = "writes"

Im MCP-Modus sind nur CODEX_BRIDGE_APPROVALS=tui (Standard) oder decline erlaubt, kein automatisches accept. Kurze Antworten direkt inline; bei mehr als 64 KiB werden sie in .bridge-output/ geschrieben, eine opaque Artifact-ID mit TTL wird zurückgegeben, die mit bridge_output_read seitenweise gelesen wird. Ein einzelner Capture hat standardmäßig maximal 10 MiB, um unbegrenzte Antworten nicht in ein einzelnes Tool-Result oder den Node-Heap zu stopfen.

Lokale Validierung:

npm test                         # 語法、unit、in-memory MCP、真實 STDIO smoke;不呼叫模型
npm run test:integration:mcp-app-server  # 真實 app-server 連線,不建立模型 turn
npm run test:spikes              # 真實 app-server regression,可能使用模型
npm run test:e2e:mcp-send        # 完整 MCP → 真實 TUI;需要 serve + 已跑完第一輪的 TUI

test:e2e:mcp-send unterscheidet sich von anderen Spikes: Es startet keinen eigenen app-server, sondern verbindet sich gemäß .bridge.json mit deinem aktuellen TUI und erstellt einen echten Turn in dem Thread, den du gerade siehst. Daher ist es nicht in test:spikes, sondern muss manuell ausgeführt werden.

Rückwärtsrichtung: Codex → Claude Code

Claude Code hat keinen gleichwertigen app-server, keinen Socket, um etwas hineinzuschieben. Es hat einen Stop-Hook: Claude führt ihn vor dem Beenden aus; wenn der Hook {"decision":"block","reason":...} zurückgibt, wird das Beenden blockiert und reason als neue Eingabe weiterverarbeitet.

Daher wird ein Postfach dazwischengeschaltet. Das Postfach ist benannt – weil mehrere Claude Code-Sessions gleichzeitig lauschen könnten; eine gemeinsame Datei würde dazu führen, dass der erste, der aufhört, auch die Nachrichten der anderen verschluckt:

# Codex 那側(或任何地方)留話
node scripts/inbox.mjs push --to bridge "順便幫我看一下 auth 那段"

# 現在有誰在聽(含各自的工作目錄)
node scripts/inbox.mjs list

# 看某個信箱(不消耗)
node scripts/inbox.mjs peek --as bridge

--to ist „für wen ist diese Nachricht“, --as ist „wer ich bin beim Lesen“, beide standardmäßig $CODEX_BRIDGE_MAILBOX und dann default.

Die .claude/settings.json dieses Repos hat bereits den Stop-Hook (Postfachname bridge) eingerichtet. Wenn Claude Code in diesem Projekt aufhört, wird das Postfach automatisch geleert und die Arbeit fortgesetzt. Diese Datei ist versioniert, daher gilt der Hook für dich, sobald du das Repo klonst und mit Claude Code öffnest – bei leerem Postfach ist er völlig still; wenn nicht gewünscht, einfach .claude/settings.json löschen. Nachrichten werden nur einmal zugestellt: drain() benennt zuerst um, dann wird gelesen, sodass gleichzeitige Schreiber keine halb gelesene Nachricht hinterlassen.

Details und Abwägungen siehe docs/reverse-channel.md.

Andere Claude Code-Sessions diese Brücke nutzen lassen

Der Server muss nur einmal gestartet werden, andere Sessions teilen ihn. Der Zustand des Skripts (.bridge.json, Postfächer) wird relativ zum Modulpfad aufgelöst, nicht zum cwd, daher funktioniert der Aufruf mit absolutem Pfad aus jedem Verzeichnis.

Vorwärtsrichtung (diese Session → Codex): Keine Einrichtung nötig, direkt aufrufen.

$bridge = "C:\path\to\claude-codex-bridge"
node "$bridge\scripts\talk.mjs" list
node "$bridge\scripts\talk.mjs" say --thread <threadId> "..."

Bei mehreren TUI-Threads unbedingt --thread verwenden – list gibt das cwd jedes Threads aus. (Oder CODEX_BRIDGE_URL setzen, um nicht auf .bridge.json angewiesen zu sein.)

Rückwärtsrichtung (Codex → diese Session): Im Projekt der Session muss der Stop-Hook in .claude/settings.json eingetragen werden, mit einem eigenen Postfachnamen:

{ "hooks": { "Stop": [ { "matcher": "*", "hooks": [
  { "type": "command",
    "command": "node \"C:/path/to/claude-codex-bridge/scripts/inbox.mjs\" hook --as web" }
] } ] } }

Beachte: $CLAUDE_PROJECT_DIR darf hier nicht verwendet werden – das würde auf das eigene Projekt verweisen, nicht auf die Bridge. Der Pfad muss absolut zur Bridge sein. Der Postfachname (im Beispiel web) ist frei wählbar, pro Session einer.

Danach kann die Codex-Seite gezielt Nachrichten senden:

node scripts/inbox.mjs push --to web "先把 CORS 那條修掉"
node scripts/inbox.mjs list       # 確認名字沒打錯、對方還活著

Die Daten für list stammen aus der Selbstregistrierung des Stop-Hooks jeder Session bei jeder Ausführung, daher muss diese Session mindestens einmal beendet worden sein, um in der Liste zu erscheinen.

Genehmigungen (wenn Codex etwas ändern will)

Wenn ein von Claude Code gesendeter Turn Befehle ausführen oder Dateien ändern soll, fordert Codex eine Genehmigung an. Der app-server broadcastet diese Anfrage an alle verbundenen Clients; wer zuerst antwortet, zählt (die anderen erhalten serverRequest/resolved). Die Standardstrategie ist daher tui: Die Bridge schweigt und überlässt die Entscheidung dem Prompt in dem Fenster, das du gerade siehst.

Wenn niemand antwortet, bleibt der gesamte Turn hängen, daher gibt es eine Sicherung: Nach CODEX_BRIDGE_APPROVAL_TIMEOUT_MS (Standard 300 Sekunden) lehnt die Bridge selbst ab (fail-closed), damit der Turn weitergehen kann.

node scripts/talk.mjs say --approvals decline "..."   # 沒開 TUI 時用
node scripts/talk.mjs say --approvals accept  "..."   # 只用在你已經信任的環境

Alternativ kann CODEX_BRIDGE_APPROVALS als Standardwert gesetzt werden.

Der Broadcast-Routing wurde unter macOS bestätigt (spike-approvals.mjs 11/11): Ein Client drückt Genehmigung, ein anderer schweigender Client erhält dieselbe Anfrage und dann serverRequest/resolved, der Turn läuft normal weiter. „Bridge schweigt = Mensch entscheidet“ ist damit gültig.

Zwei Einstellungen, die „dem TUI-Menschen überlassen“ stillschweigend unwirksam machen

Bevor eine Genehmigungsanfrage einen Client erreicht, durchläuft sie die eigenen codex-Einstellungen des Benutzers. Wenn eine der folgenden beiden aktiv ist, wird dein TUI gar nicht gefragt, und das Schweigen der Bridge bedeutet nicht, dass der Mensch entscheidet:

Einstellung

Ort

Effekt

PermissionRequest-Hook

~/.codex/hooks.json

Hook fängt die Genehmigungsanfrage ab. macOS-Test: Bei aktivem Hook erhalten zwei Clients keine einzige Genehmigungsanfrage, die Datei wird trotzdem geschrieben

approvals_reviewer = "auto_review"

~/.codex/config.toml

Wird einem Subagent überlassen, der je nach Risiko automatisch entscheidet, ohne den Menschen zu fragen

Beides sind sinnvolle persönliche Einstellungen, die dieses Projekt nicht ändert; man muss nur wissen: Wenn sie aktiv sind, ist das „Mensch“ bei --approvals tui in Wirklichkeit sie. Um zu prüfen, welcher Fall auf deinem Rechner vorliegt, führe spike-approvals.mjs aus – dieser Spike startet seinen eigenen app-server mit --disable hooks -c approvals_reviewer=user, um beide auszuschalten, und misst nur das Protokoll selbst.

Das Antwortformat verschiedener Genehmigungsanfragen ist nicht einheitlich – nur zwei item/*/requestApproval akzeptieren {decision:"decline"}; item/permissions/requestApproval erwartet ein (leeres) Berechtigungsprofil, die alten execCommandApproval / applyPatchApproval erwarten {decision:{denied:{rejection}}}. Die falsche Form führt zu einem Schema-Fehler, keiner höflichen Ablehnung. Eine Vergleichstabelle findet sich in src/appServerWsClient.mjs unter DEFAULT_SERVER_REQUEST_RESPONSES.

Warum nicht andere Ansätze

Ansatz

Problem

wezterm cli send-text / get-text

Erfasst das gerenderte TUI: Rahmenzeichen, Spinner, Zeilenumbrüche; „Ist die Antwort fertig?“ nur durch Pollen der Bildschirmänderungen erkennbar

Gemeinsames Datei-Postfach (Vorwärtsrichtung)

Machbar, aber kein Echtzeit-Status, und Auslösung erfordert manuelles Eingreifen

/codex:rescue-Subagent

Kalter Start jedes Mal, separate Session, erreicht nicht das TUI vor dir

Diese Lösung

Strukturierte Ereignisse; turn/steer kann sogar in einen laufenden Turn eingreifen

Die Rückwärtsrichtung bleibt ein Datei-Postfach – aber weil Claude Code keinen anschließbaren Socket hat und der Stop-Hook das „Auslösen“ ohne manuelles Eingreifen ermöglicht.

Sicherheit

  • Listener bindet an Loopback. --ws-auth wirkt nur bei non-Loopback, daher lokal kein Token nötig.

  • Genehmigungen standardmäßig dem Menschen überlassen (tui), bei Timeout fail-closed. Nicht-Genehmigungs-Server→Client-Anfragen (Tool-Aufrufe, MCP-Elicitation) immer fail-closed – die Bridge hat keine UI, um einen Menschen zu fragen.

Validiert / Nicht validiert

Drei Spikes, jeder startet seinen eigenen app-server (ephemeraler Port), ohne den Thread, den du gerade siehst, zu beeinflussen:

node scripts/spike-multiclient.mjs   # 9/9   兩個 client 共用一條 thread
node scripts/spike-multithread.mjs   # 7/7   兩條 thread 同時跑,回覆不串味
node scripts/spike-approvals.mjs     # 11/11 於 macOS;Windows 上 3 項 SKIP,見下

macOS (26.5.1, Node v24.19.0 LTS, codex-cli 0.147.0) Testergebnisse: Alle drei Spikes bestanden, npm test 21/21, npm run test:integration:mcp-app-server PASS. scripts/serve.mjs --cwd, scripts/talk.mjs list, scripts/inbox.mjs (push / list / peek / hook, inklusive Chinesisch) wurden ebenfalls unter macOS manuell getestet; der inbox-Hook läuft sogar mit Node 20, daher muss der Stop-Hook keine Node-Version wählen.

Zusätzlich mit echtem TUI (codex --remote) bestätigt: Die von Claude Code gesendete Nachricht wird im TUI als Benutzernachricht dargestellt, Codex antwortet normal, der Antwortstrom geht zurück zu Claude Code.

Geklärt (codex-cli 0.147.0):

  • Benachrichtigungen enthalten immer threadId, Streaming-Typen (item/agentMessage/delta) zusätzlich turnId, und die von turn/start zurückgegebene turn.id ist identisch mit der im Stream. Früher wurde angenommen, „Benachrichtigungen enthalten kein threadId“, tatsächlich war es ein Symptom dafür, dass der Client dem Thread nicht beigetreten war.

  • Der Thread muss zuerst mit thread/resume beigetreten sein, um Benachrichtigungen zu erhalten; und der Thread muss die erste Dialogrunde durchlaufen haben, um resumable zu sein.

  • historyMode: "paginated" (vom TUI erstellter Thread) → thread/read mit includeTurns schlägt fehl (list_turns is not supported yet). Historische Daten nachträglich abzurufen, ist derzeit nicht möglich; Antworten erfolgen über den Live-Stream.

  • Genehmigungen werden an alle Clients gebroadcastet, der erste, der antwortet, zählt (macOS bestätigt). Der schweigende Client erhält ebenfalls die Anfrage und später serverRequest/resolved, der Turn bleibt nicht hängen. Auf Maschinen, auf denen der OS-Sandbox-Helper nicht startet (bei bestimmten verwalteten Unternehmens-Windows kann ShellExecuteExW failed to launch setup helper: 1223 auftreten), schlägt das Schreiben von Dateien bereits vor dem „Menschen fragen“ fehl, die Genehmigungsanfrage wird gar nicht gesendet, daher werden diese drei als SKIP markiert – Umgebungseinschränkung, kein Protokollproblem.

  • Der PermissionRequest-Hook auf Benutzerebene fängt alle Genehmigungsanfragen ab, der Client erhält keine einzige (macOS getestet). Details siehe oben im Abschnitt „Genehmigungen“.

Nicht validiert:

  • turn/steer (in einen laufenden Turn eingreifen) wurde nur das Schema gelesen, nicht getestet.

  • -C nicht getestetGetestet: Wenn das TUI ohne -C verbindet, ist das cwd des Threads das cwd des app-servers, unabhängig davon, in welchem Verzeichnis codex --remote ausgeführt wird; mit -C <dir> wird dieses Verzeichnis verwendet. (macOS, mit pty gestartetem TUI, anderer Client liest thread/read.)

  • Das gesamte Protokoll ist als [experimental] markiert, Codex-Updates können es ändern.

Referenzen

Warum der Stop-Hook für die Rückwärtsrichtung und kein anderer Mechanismus: docs/reverse-channel.md

Protokoll-Schema: codex app-server generate-json-schema --out <dir>

A
license - permissive license
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    The self-hosted MCP bridge between Claude Chat and Claude Code.
    46
    AGPL 3.0
  • A
    license
    Not graded
    quality
    D
    maintenance
    Connects Claude Desktop and Claude Code, enabling autonomous exchange of messages, files, and code while keeping their context windows separate.
    4
    MIT

View all related MCP servers

Related MCP Connectors

  • Stop copy-pasting between Claude Chat and Claude Code.

  • Real-time chat hub for AI agents — Claude Code, Cursor, Cline, Codex over MCP or REST.

  • Trade Robinhood through natural language in Claude Code.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ar36planet/claude-codex-bridge-public'

If you have feedback or need assistance with the MCP directory API, please join our Discord server