claude-codex-bridge
claude-codex-bridge
Позволяет Claude Code общаться с тем самым Codex TUI, который у вас перед глазами — и вы всё видите. Наоборот, Codex может отправлять сообщения в сессию, в которой работает Claude Code.
Это не захват экрана, не опрос файлов, не headless subagent. Оба подключены к одному и тому же потоку codex app-server: сообщение, отправленное Claude Code, мгновенно появляется в TUI, который вы видите.
English: README.en.md
Инструкция по установке: SETUP.md (中文) · SETUP.en.md (English)
В этом README описаны обоснование дизайна и записи проверок — почему выбран такой подход, какие факты уже измерены, а какие ещё нет. Если хотите сразу запустить, быстрее будет через SETUP.
Среда проверки: codex-cli 0.147.0, протестировано на Windows 11 и macOS 26, оба работают на Node 24 LTS (Krypton). Код не привязан к платформе (пути везде через node:path, resolveCodex() — единственное исключение для Windows). Различия и результаты для обеих платформ см. в разделе «Проверено / не проверено».
Требования к Node: >=22 (engines в package.json), рекомендуется v24 LTS. При каждом push в CI запускается полная матрица ubuntu / macOS / Windows × Node 22, 24.
codex часто является глобальным пакетом под какой-то версией nvm, и эта версия может быть ниже 22 — тогда node по умолчанию тоже устаревает. Самый чистый способ — держать node и codex в одном LTS:
nvm install 24 && nvm alias default 24
nvm reinstall-packages 20 # 把 codex 等全域套件搬過去(20 換成你原本的版本)(Бин codex — это shim с #!/usr/bin/env node, он запускается с node из PATH, не привязан к версии при установке.)
Архитектура
┌──────────────────────────────┐
│ 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)Ключевой момент в прямом направлении — семантика thread/resume:
If thread_id identifies a running thread, app-server rejoins that thread.
Таким образом, второй клиент не открывает новый диалог и не воспроизводит сохранённое — он присоединяется к тому же самому выполняющемуся потоку. Только после присоединения он сможет получать поток уведомлений этого потока; просто подключиться к endpoint недостаточно.
Related MCP server: Claude-Gemini MCP Integration Server
Использование
Три окна.
1. Общий сервер (держите открытым, не закрывайте)
node scripts/serve.mjs --cwd C:\path\to\你的專案--cwd — это рабочая директория, где фактически работает Codex. Если поток не указывает свою cwd, он использует cwd app-server, поэтому без этого параметра он остановится в директории, откуда вы запустили скрипт (то есть в папке самого bridge). Можно также использовать переменную окружения CODEX_BRIDGE_CWD.
Порт задаётся через --port или CODEX_BRIDGE_PORT (по умолчанию 8787). Endpoint и workspace записываются в .bridge.json, talk.mjs читает их автоматически.
2. Codex TUI, который вы хотите видеть
codex --remote ws://127.0.0.1:8787 -C C:\path\to\你的專案-C фиксирует workspace этого окна; если не указан, используется --cwd из пункта выше.
Сначала скажите что-нибудь в TUI и дождитесь ответа. Поток становится возобновляемым только после завершения первого раунда диалога; до этого thread/resume вернёт no rollout found for thread id.
Обратите внимание на ловушку: talk.mjs list видит этот поток ещё до этого — как только TUI подключается, поток создаётся и появляется в thread/loaded/list. Так что «видно в list» не означает «можно отправлять сообщения». Если пропустить этот шаг, say отправится, Codex ответит в TUI, но bridge не получит поток ответа и просто дождётся таймаута (на macOS этот симптом проверен).
3. Со стороны Claude Code
node scripts/talk.mjs list # 列出活著的 thread(含各自的 cwd)
node scripts/talk.mjs say "跑一下測試" # 送話進去,你會在 TUI 看到
node scripts/talk.mjs read # 讀完整 thread(結構化 JSON)Если поток только один, say / read выберут его автоматически; если несколько, нужно указать --thread <id> — не угадываем, с какой сессией вы говорите. list выводит cwd каждого потока, что помогает различать их при нескольких открытых.
say также принимает --cwd <dir> (меняет рабочую директорию только для этого раунда) и --approvals (см. ниже).
MCP-интерфейс
CLI по-прежнему можно использовать напрямую; MCP-интерфейс предоставляет структурированные инструменты с той же основной функциональностью. Оба направления используют один и тот же код, но каждый инициатор запускает свой STDIO-процесс:
--role claude: Claude Code активно отправляет сообщения в поток Codex.--role codex: Codex активно помещает сообщения в почтовый ящик Claude.
Шаги установки
0. Проверьте предварительные условия
Node
>=22(см. примечание о версиях в начале).В этом репозитории уже выполнен
npm install.MCP — это только интерфейс, не транспортный уровень. Для работы ему всё равно нужен запущенный
serve.mjsи подключённый TUI, чтобы было с чем общаться — см. «Использование» выше.
cd <這個 repo>
npm install1. Решите, какую сторону устанавливать
Что вы хотите | Что устанавливать |
Только Claude Code может отправлять сообщения в Codex | Только |
Только Codex может оставлять сообщения для Claude Code | Только |
Двусторонняя связь | Установите обе |
Если нужна только односторонняя связь, не устанавливайте обе стороны. Пассивная приёмная сторона не использует MCP — она работает через app-server/TUI и Stop hook от Claude соответственно.
2. Установка
Со стороны Claude Code (--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 (--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 запишет в .mcp.json этого проекта; для общего использования между проектами замените на --scope user.
Путь должен быть абсолютным, но «в какой директории запускать» не влияет на результат — все файлы состояния (.bridge.json, .bridge-inbox/, .bridge-output/) разрешаются относительно расположения модуля, а не cwd. Так что одной установки достаточно, не нужно устанавливать отдельно для каждого проекта.
3. Перезапустите
claude mcp add / codex mcp add только изменяют файлы конфигурации, уже запущенная сессия не загрузит новый MCP-сервер. После установки закройте и снова откройте эту сессию, чтобы инструменты стали доступны.
4. Проверьте установку
В перезапущенной сессии вызовите bridge_status. Если ok: true и role правильный, всё в порядке.
Затем codex_threads_list должен показать ваш поток TUI (включая его cwd).
Если не хотите открывать сессию, можно проверить тот же путь из командной строки:
npm run test:e2e:mcp-send # 需要 serve + 已跑完第一輪的 TUIИнструменты
Роль | Инструменты |
Общие |
|
Claude |
|
Codex |
|
codex_message_send ожидает завершения всего раунда, поэтому для MCP-конфигурации Codex рекомендуется увеличить таймаут и требовать подтверждения для write-инструментов:
[mcp_servers.claude-codex-bridge]
tool_timeout_sec = 360
default_tools_approval_mode = "writes"В режиме MCP допускается только CODEX_BRIDGE_APPROVALS=tui (по умолчанию) или decline, автоматическое accept не принимается.
Короткие ответы отправляются inline; если превышают 64 KiB, записываются в .bridge-output/, возвращается opaque artifact ID с TTL, который затем читается постранично через bridge_output_read. Один capture по умолчанию не более 10 MiB, чтобы не помещать неограниченный ответ в один tool result или кучу Node.
Локальная проверка:
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 отличается от других spike: он не запускает отдельный app-server, а подключается к вашему текущему TUI через .bridge.json и создаёт реальный раунд в потоке, который вы видите. Поэтому он не входит в test:spikes и запускается отдельно.
Обратное направление: Codex → Claude Code
У Claude Code нет аналога app-server, нет сокета, в который можно что-то отправить. У него есть Stop hook: он запускается перед тем, как Claude завершает работу; если hook возвращает {"decision":"block","reason":...}, он заставляет Claude не останавливаться и продолжает работу с reason в качестве нового ввода.
Поэтому мы используем почтовый ящик. Почтовый ящик именованный — потому что может быть несколько сессий Claude Code, слушающих одновременно; если использовать общий файл, кто первый завершится, тот и проглотит чужие письма:
# Codex 那側(或任何地方)留話
node scripts/inbox.mjs push --to bridge "順便幫我看一下 auth 那段"
# 現在有誰在聽(含各自的工作目錄)
node scripts/inbox.mjs list
# 看某個信箱(不消耗)
node scripts/inbox.mjs peek --as bridge--to — «кому предназначено это письмо», --as — «кто я, читающий», оба по умолчанию берут $CODEX_BRIDGE_MAILBOX, затем default.
В этом репозитории .claude/settings.json уже содержит Stop hook (имя почтового ящика bridge). Когда Claude Code завершает работу в этом проекте, он автоматически очищает почтовый ящик и продолжает работу. Этот файл находится под контролем версий, поэтому если вы клонируете репозиторий и откроете его в Claude Code, этот hook будет активен — когда почтовый ящик пуст, он полностью молчалив; если не нужен, просто удалите .claude/settings.json. Сообщение доставляется только один раз: drain() сначала переименовывает, затем читает, так что одновременная запись не приведёт к чтению наполовину.
Подробности и компромиссы см. в docs/reverse-channel.md.
Как использовать этот мост с другими сессиями Claude Code
Сервер запускается один раз, остальные сессии используют его совместно. Состояние скрипта (.bridge.json, почтовые ящики) разрешается относительно расположения самого модуля, а не cwd, поэтому вызов с абсолютным путём из любой директории корректен.
Прямое направление (эта сессия → Codex): настройка не требуется, вызывайте напрямую.
$bridge = "C:\path\to\claude-codex-bridge"
node "$bridge\scripts\talk.mjs" list
node "$bridge\scripts\talk.mjs" say --thread <threadId> "..."При нескольких открытых TUI обязательно используйте --thread — list покажет cwd каждого потока для идентификации.
(Или установите CODEX_BRIDGE_URL, чтобы не зависеть от .bridge.json.)
Обратное направление (Codex → эта сессия): нужно добавить Stop hook в .claude/settings.json этого проекта и дать ему своё имя почтового ящика:
{ "hooks": { "Stop": [ { "matcher": "*", "hooks": [
{ "type": "command",
"command": "node \"C:/path/to/claude-codex-bridge/scripts/inbox.mjs\" hook --as web" }
] } ] } }Обратите внимание: нельзя использовать $CLAUDE_PROJECT_DIR — он укажет на сам проект, а не на bridge. Путь должен быть жёстко прописан до bridge. Имя почтового ящика (в примере web) выбираете сами, по одному на сессию.
После этого со стороны Codex можно отправлять письма по имени:
node scripts/inbox.mjs push --to web "先把 CORS 那條修掉"
node scripts/inbox.mjs list # 確認名字沒打錯、對方還活著Данные list берутся из саморегистрации Stop hook каждой сессии при каждом выполнении, поэтому сессия должна завершиться хотя бы один раз, чтобы появиться в списке.
Подтверждения (когда Codex хочет что-то изменить)
Если раунд, отправленный Claude Code, требует выполнения команд или изменения файлов, Codex отправляет запрос на подтверждение. app-server рассылает такой запрос всем подключённым клиентам; кто первый ответит, тот и считается (остальные получают serverRequest/resolved). Поэтому стратегия по умолчанию — tui: bridge молчит, решение принимает подсказка в окне, которое вы видите.
Если никто не ответит, весь раунд зависнет, поэтому есть защита: если по истечении CODEX_BRIDGE_APPROVAL_TIMEOUT_MS (по умолчанию 300 секунд) никто не ответил, bridge сам отклоняет (fail-closed), и раунд продолжается.
node scripts/talk.mjs say --approvals decline "..." # 沒開 TUI 時用
node scripts/talk.mjs say --approvals accept "..." # 只用在你已經信任的環境Можно также установить значение по умолчанию через CODEX_BRIDGE_APPROVALS.
Маршрутизация рассылки подтверждена на macOS (spike-approvals.mjs 11/11): один клиент нажимает подтверждение, другой молчаливый клиент получает тот же запрос, затем получает serverRequest/resolved, раунд завершается нормально. Таким образом, «молчание bridge = решение за человеком» работает.
Две настройки, которые незаметно отключают «передачу решения человеку в TUI»
Прежде чем запрос на подтверждение достигнет любого клиента, он сначала проходит через настройки codex самого пользователя. Если активно любое из следующих двух, ваш TUI вообще не получит запрос, и молчание bridge не будет означать, что решение принимает человек:
Настройка | Местоположение | Эффект |
Hook |
| Hook перехватывает запрос на подтверждение. На macOS проверено: при активном hook оба клиента не получают ни одного запроса на подтверждение, файлы всё равно записываются |
|
| Передаётся subagent, который автоматически решает на основе риска, не спрашивая человека |
Обе настройки являются разумными личными настройками; этот проект не будет их изменять; просто нужно знать: когда они активны, «человек» в --approvals tui — это на самом деле они. Чтобы проверить, какая ситуация на вашей машине, запустите spike-approvals.mjs — этот spike запускает свой собственный app-server с --disable hooks -c approvals_reviewer=user, отключая обе настройки, и измеряет сам протокол.
Форматы ответов на различные запросы подтверждения не единообразны — только два item/*/requestApproval принимают {decision:"decline"}; item/permissions/requestApproval требует (пустой) профиль разрешений, старые execCommandApproval / applyPatchApproval требуют {decision:{denied:{rejection}}}. Неправильная форма — это ошибка схемы, а не вежливый отказ. Таблица соответствия в DEFAULT_SERVER_REQUEST_RESPONSES в src/appServerWsClient.mjs.
Почему не другие подходы
Подход | Проблема |
| Захватывает отрендеренный TUI: символы рамок, спиннеры, обрывы строк; определить, «закончил ли ответ», можно только опросом изменений экрана |
Общий файловый почтовый ящик (прямое направление) | Работоспособно, но не видно состояния в реальном времени, и требуется ручное вмешательство для триггера |
Subagent | Каждый раз холодный старт, отдельная сессия, не подключается к тому TUI, который вы видите |
Данное решение | Структурированные события; |
Обратное направление по-прежнему использует файловый почтовый ящик — но потому что у Claude Code нет сокета для подключения, а Stop hook делает «триггер» автоматическим, без ручного вмешательства.
Безопасность
Listener привязан к loopback.
--ws-authдействует только на non-loopback, поэтому на локальной машине токен не требуется.Подтверждения по умолчанию передаются человеку (
tui), при таймауте fail-closed. Запросы от сервера к клиенту, не связанные с подтверждением (вызов инструментов, MCP elicitation), всегда fail-closed — у bridge нет UI, чтобы спрашивать человека.
Проверено / не проверено
Три spike, каждый запускает свой собственный app-server (ephemeral port), не затрагивая ваш текущий поток:
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) результаты: все три spike пройдены, 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, включая китайский) также протестированы вручную на macOS;
inbox hook работает даже на Node 20, так что Stop hook не требует определённой версии node.
Кроме того, проверено на реальном TUI (codex --remote): сообщение, отправленное Claude Code, отображается в TUI как сообщение пользователя, Codex нормально отвечает, поток ответа возвращается в Claude Code.
Выяснено (codex-cli 0.147.0):
Уведомления всегда содержат
threadId, потоковые (например,item/agentMessage/delta) дополнительно содержатturnId, иturn.id, возвращаемыйturn/start, полностью совпадает с тем, что в потоке. Ранее считалось, что «уведомления не содержат threadId», на самом деле это был симптом того, что клиент не присоединился к потоку.Поток должен быть сначала присоединён через
thread/resume, чтобы получать уведомления; а поток становится возобновляемым только после завершения первого раунда диалога.historyMode: "paginated"(поток, созданный TUI) →thread/readсincludeTurnsзавершается ошибкой (list_turns is not supported yet). Получение истории постфактум в настоящее время не работает; ответы получаются через поток в реальном времени.Подтверждения рассылаются всем клиентам, засчитывается ответ первого (подтверждено на macOS). Молчаливый клиент также получает запрос, затем получает
serverRequest/resolved, раунд не зависает. На машинах, где не запускается OS sandbox helper (на некоторых управляемых корпоративных Windows может появлятьсяShellExecuteExW failed to launch setup helper: 1223), запись файла завершается ошибкой до того, как будет задан вопрос человеку, и запрос на подтверждение вообще не отправляется, поэтому эти три пункта будут помечены как SKIP — ограничение среды, не проблема протокола.Пользовательский hook
PermissionRequestполностью перехватывает запросы на подтверждение, клиент не получает ни одного (проверено на macOS). Подробности см. в разделе «Подтверждения» выше.
Не проверено:
turn/steer(вмешательство в выполняющийся раунд) — только читал схему, не тестировал.-Cне тестировал→ уже протестировано: когда TUI подключается без-C, cwd потока — это cwd app-server, независимо от того, в какой директории вы запустилиcodex --remote; с-C <dir>cwd меняется на указанную директорию. (macOS, с запуском TUI через pty, другой клиент читаетthread/read.)Весь протокол помечен как
[experimental], может измениться при обновлении codex.
Ссылки
Почему обратное направление использует Stop hook, а не другой механизм:
docs/reverse-channel.md
Схема протокола: codex app-server generate-json-schema --out <dir>
This server cannot be deployed
Maintenance
Related MCP Connectors
Real-time chat for AI agents. Claude Code, Cursor, Cline and Codex join channels over MCP.
Share context and questions between Claude instances — VS Code, claude.ai web, and mobile.
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.
Related MCP Servers
- AlicenseNot gradedqualityFmaintenanceThe 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.729 npm3MIT