claude-codex-bridge
claude-codex-bridge
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 installeingerichtet.MCP ist nur die Schnittstelle, nicht die Transportschicht. Es benötigt weiterhin einen laufenden
serve.mjsund ein verbundenes TUI, um etwas sagen zu können – siehe „Verwendung“ oben.
cd <這個 repo>
npm install1. Entscheiden, welche Seite installiert werden soll
Gewünschte Funktion | Installation |
Nur Claude Code kann zu Codex sprechen | Nur |
Nur Codex kann Nachrichten für Claude hinterlassen | Nur |
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 claudeCodex-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 + 已跑完第一輪的 TUITools
Rolle | Tools |
Gemeinsam |
|
Claude |
|
Codex |
|
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 + 已跑完第一輪的 TUItest: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 |
|
| Hook fängt die Genehmigungsanfrage ab. macOS-Test: Bei aktivem Hook erhalten zwei Clients keine einzige Genehmigungsanfrage, die Datei wird trotzdem geschrieben |
|
| 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 |
| 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 |
| Kalter Start jedes Mal, separate Session, erreicht nicht das TUI vor dir |
Diese Lösung | Strukturierte Ereignisse; |
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-authwirkt 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ätzlichturnId, und die vonturn/startzurückgegebeneturn.idist 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/resumebeigetreten 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/readmitincludeTurnsschlä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 kannShellExecuteExW failed to launch setup helper: 1223auftreten), 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.-Cnicht getestet→ Getestet: Wenn das TUI ohne-Cverbindet, ist das cwd des Threads das cwd des app-servers, unabhängig davon, in welchem Verzeichniscodex --remoteausgeführt wird; mit-C <dir>wird dieses Verzeichnis verwendet. (macOS, mit pty gestartetem TUI, anderer Client liestthread/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>
This server cannot be installed
Maintenance
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
- AlicenseNot gradedqualityBmaintenanceThe self-hosted MCP bridge between Claude Chat and Claude Code.46AGPL 3.0
- AlicenseNot gradedqualityDmaintenanceBridges Claude Code and Google's Gemini AI models to enable AI-to-AI collaboration for code reviews, brainstorming, and direct questions.5MIT
- AlicenseNot gradedqualityDmaintenanceConnects Claude Desktop and Claude Code, enabling autonomous exchange of messages, files, and code while keeping their context windows separate.4MIT
- AlicenseAqualityAmaintenanceBridges Claude Code and OpenAI Codex CLI for an interactive plan-execute-review workflow, enabling Claude to interview, design, and review while Codex implements code changes.74753MIT
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.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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