claude-codex-bridge
claude-codex-bridge
Claude Code가 당신 앞에 있는 Codex TUI에 말을 걸도록 합니다 — 당신은全程 볼 수 있습니다. 반대로, Codex도 Claude Code가 실행 중인 session에 메시지를 보낼 수 있습니다.
화면 캡처, 파일 폴링, headless subagent가 아닙니다. 양쪽이 같은
codex app-server의 같은 thread에 연결됩니다: 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입니다(package.json의 engines). 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의 bin은 #!/usr/bin/env node shim이며, PATH에 있는 node를 따라 실행되고,
설치 시의 버전에 묶이지 않습니다.)
아키텍처
┌──────────────────────────────┐
│ 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의 의미입니다:
thread_id가 실행 중인 thread를 식별하면, app-server는 해당 thread에 다시 참여합니다.
따라서 두 번째 클라이언트는 새 대화를 시작하거나 저장된 것을 재생하는 것이 아니라 동일한 실행 중인 thread에 참여합니다. 참여한 후에야 해당 thread의 알림 스트림을 수신할 수 있습니다. 단순히 endpoint에 연결하는 것만으로는 충분하지 않습니다.
Related MCP server: Claude-Gemini MCP Integration Server
사용법
세 개의 창.
1. 공유 서버(열어 두고 닫지 마세요)
node scripts/serve.mjs --cwd C:\path\to\你的專案--cwd는 Codex가 실제로 작업하는 디렉터리입니다. thread가 자체 cwd를 지정하지 않으면 app-server의 cwd를
사용하므로, 이 매개변수를 제공하지 않으면 스크립트를 시작한 위치(즉, bridge 자체의 폴더)에서 멈춥니다.
환경 변수 CODEX_BRIDGE_CWD를 사용할 수도 있습니다.
port는 --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는 첫 번째 대화가 '완료'되어야
resumable 상태가 됩니다. 그 전에 thread/resume은 no rollout found for thread id를 반환합니다.
이 함정에 주의하세요: talk.mjs list는 그 전에 이미 해당 thread를 볼 수 있습니다 — TUI가 연결되면
thread가 생성되고 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)thread가 하나만 있을 때 say / read는 자동으로 선택합니다; 여러 개일 때는 --thread <id>로 지정해야 합니다 —
어느 session과 대화 중인지 추측하지 않습니다. list는 각 thread의 cwd를 함께 출력하므로, 여러 개를 열 때
이것으로 구분합니다.
say는 추가로 --cwd <dir>(이번 한 번만 작업 디렉터리 변경)과 --approvals(아래 참조)를 받습니다.
MCP 인터페이스
CLI는 여전히 직접 사용할 수 있습니다; MCP 인터페이스는 동일한 핵심 기능을 구조화된 tools로 제공합니다. 두 방향은 동일한 코드를 공유하지만, 능동적으로 메시지를 보내는 쪽은 각각 STDIO 프로세스를 시작합니다:
--role claude: Claude Code가 Codex thread에 능동적으로 메시지를 보냅니다.--role codex: Codex가 Claude mailbox에 메시지를 넣습니다.
설치 단계
0. 먼저 전제 조건 확인
Node
>=22(처음의 버전 설명 참조).이 repo에서
npm install을 실행했는지 확인.MCP는 인터페이스일 뿐, 전송 계층이 아닙니다. 여전히 실행 중인
serve.mjs와 연결된 TUI가 있어야 대화할 대상이 생깁니다 — 위의 '사용법' 참조.
cd <這個 repo>
npm install1. 어느 쪽을 설치할지 결정
원하는 것 | 설치할 것 |
Claude Code가 Codex에만 메시지를 보낼 수 있음 |
|
Codex가 Claude Code에만 메시지를 남길 수 있음 |
|
양방향 | 양쪽 모두 설치 |
단방향만 필요한 경우 양쪽을 설치하지 마세요. 수동 수신 쪽은 MCP에 의존하지 않습니다 — 각각 app-server/TUI 및 Claude의 Stop hook을 사용합니다.
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 claudeCodex 쪽 (--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는 설정 파일만 변경합니다. 이미 실행 중인 session은 새 MCP server를 로드하지 않습니다.
설치 후 해당 session을 닫고 다시 열어야 tools를 호출할 수 있습니다.
4. 설치 확인
다시 시작한 session에서 bridge_status를 호출하세요. ok: true이고 role이 올바르면 성공입니다.
그 다음 codex_threads_list에서 TUI의 thread(해당 cwd 포함)를 볼 수 있어야 합니다.
session을 열지 않고도 명령줄에서 동일한 경로를 확인할 수 있습니다:
npm run test:e2e:mcp-send # 需要 serve + 已跑完第一輪的 TUITools
Role | Tools |
공유 |
|
Claude |
|
Codex |
|
codex_message_send는 전체 turn을 기다리므로, Codex의 MCP 설정에서는 timeout을 늘리고 write tools에
승인이 필요하도록 설정하는 것이 좋습니다:
[mcp_servers.claude-codex-bridge]
tool_timeout_sec = 360
default_tools_approval_mode = "writes"MCP 모드는 CODEX_BRIDGE_APPROVALS=tui(기본값) 또는 decline만 허용하며, 자동 accept는 허용하지 않습니다.
짧은 응답은 인라인으로; 64 KiB를 초과하면 .bridge-output/에 기록하고, TTL이 있는 불투명한 artifact ID를 반환한 후,
bridge_output_read로 페이지 단위로 읽습니다. 단일 capture는 기본적으로 최대 10 MiB이며, 무제한 reply를
한 번의 tool result 또는 Node heap에 넣지 않습니다.
로컬 검증:
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를 시작하지 않고, .bridge.json에 따라
현재 보고 있는 TUI에 연결하여, 보고 있는 thread에서 실제 turn을 생성합니다. 따라서 test:spikes에 포함되지 않으며,
직접 실행해야 합니다.
반대 방향: Codex → Claude Code
Claude Code에는 app-server에 해당하는 것이 없고, socket을 통해 무언가를 밀어넣을 수 없습니다. 대신 Stop hook이 있습니다:
Claude가 작업을 마치기 전에 실행되며, hook이 {"decision":"block","reason":...}을 반환하면 멈추지 않고
reason을 새로운 입력으로 계속 처리하도록 지시합니다.
따라서 중간에 mailbox를 둡니다. mailbox는 이름이 지정됩니다 — 여러 Claude Code session이 동시에 듣고 있을 수 있기 때문에, 하나의 파일을 공유하면 먼저 끝나는 쪽이 다른 사람의 메시지도 가져갈 수 있습니다:
# 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로 대체됩니다.
이 repo의 .claude/settings.json에는 이미 Stop hook(mailbox 이름 bridge)이 연결되어 있습니다.
이 프로젝트에서 Claude Code를 사용할 때, 작업을 마치면 자동으로 mailbox를 비우고 계속 진행합니다. 해당 파일은 버전 관리에 포함되어 있으므로,
repo를 clone하고 Claude Code로 열면 이 hook이 자동으로 적용됩니다 — mailbox가 비어 있을 때는 완전히 조용하며,
필요하지 않으면 .claude/settings.json을 삭제하면 됩니다. 메시지는 한 번만 전달됩니다:
drain()은 먼저 rename한 후 읽으므로, 동시에 쓰는 사람이 읽는 도중에 가져가지 않습니다.
자세한 내용과 절충 사항은 docs/reverse-channel.md를 참조하세요.
다른 Claude Code session도 이 bridge를 사용하도록 설정
server는 한 번만 시작하면 되며, 다른 session이 공유합니다. 스크립트의 상태(.bridge.json, mailbox)는
모듈 자체 위치를 기준으로 해석되며, cwd를 보지 않으므로 어떤 디렉터리에서 절대 경로로 호출해도 올바릅니다.
정방향(해당 session → 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가 각 thread의 cwd를 출력하여 구분할 수 있습니다.
(또는 CODEX_BRIDGE_URL을 설정하면 .bridge.json에 의존하지 않아도 됩니다.)
반대 방향(Codex → 해당 session): 해당 프로젝트의 .claude/settings.json에 Stop hook을 연결하고,
자신의 mailbox 이름을 지정해야 합니다:
{ "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로 절대 경로를 지정해야 합니다. mailbox 이름(위 예제의 web)은 자유롭게 정하며, 각 session마다 하나씩입니다.
그러면 Codex 쪽에서 이름을 지정하여 메시지를 보낼 수 있습니다:
node scripts/inbox.mjs push --to web "先把 CORS 那條修掉"
node scripts/inbox.mjs list # 確認名字沒打錯、對方還活著list의 데이터는 각 session의 Stop hook이 실행될 때마다 자체 등록한 정보를 기반으로 하므로, 해당 session이
적어도 한 번 작업을 마쳐야 목록에 나타납니다.
승인(Codex가 무언가를 변경해야 할 때)
Claude Code가 보낸 turn이 명령을 실행하거나 파일을 수정해야 하는 경우, Codex는 승인 요청을 보냅니다.
app-server는 이 요청을 연결된 모든 클라이언트에 브로드캐스트하며, 먼저 응답하는 클라이언트의 응답이 적용됩니다(늦은 클라이언트는
serverRequest/resolved를 수신합니다). 따라서 기본 전략은 tui입니다: bridge는 침묵을 유지하고, 당신 앞의 창에서
프롬프트가 결정하도록 합니다.
아무도 응답하지 않으면 전체 turn이 멈출 수 있으므로, 안전 장치가 있습니다: CODEX_BRIDGE_APPROVAL_TIMEOUT_MS
(기본값 300초)를 초과하면 bridge가 fail-closed로 거부하고, turn이 계속 진행됩니다.
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를 수신하며, turn은 정상적으로 진행됩니다.
따라서 'bridge 침묵 = 사람이 결정하도록 함'이 성립합니다.
'TUI의 사람이 결정하도록 함'을 조용히 무효화하는 두 가지 설정
승인 요청이 클라이언트에 도달하기 전에, 먼저 사용자 자신의 codex 설정을 통과합니다. 다음 중 하나라도 활성화되어 있으면, 당신 앞의 TUI는 전혀 질문을 받지 않으며, bridge의 침묵이 사람이 결정하는 것과 같지 않습니다:
설정 | 위치 | 효과 |
|
| 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}}}을 요구합니다. 잘못된 형식은 schema 오류지, 예의 바른 거절이 아닙니다.
대조표는 src/appServerWsClient.mjs의 DEFAULT_SERVER_REQUEST_RESPONSES에 있습니다.
왜 다른 방법을 사용하지 않았는가
방법 | 문제 |
| TUI 렌더링 후의 화면을 캡처: 테두리 문자, spinner, 줄바꿈 잘림; '응답 완료' 판단은 화면 변화를 폴링해야 함 |
공유 파일 mailbox (정방향) | 가능하지만 실시간 상태를 볼 수 없고, 트리거에 수동 개입이 필요함 |
| 매번 콜드 스타트, 독립 session, 당신 앞의 TUI에 연결되지 않음 |
이 방법 | 구조화된 이벤트; |
반대 방향은 여전히 파일 mailbox입니다 — 그러나 Claude Code에는 연결할 수 있는 socket이 없고, Stop hook을 통해 '트리거'에 수동 개입이 필요하지 않기 때문입니다.
보안
Listener는 loopback에 바인딩됩니다.
--ws-auth는 non-loopback에만 적용되므로, 로컬에서는 token이 필요 없습니다.승인은 기본적으로 사람에게 맡깁니다(
tui), 타임아웃 시 fail-closed. 승인이 아닌 server→client 요청 (도구 호출, MCP elicitation)은 항상 fail-closed됩니다 — bridge에는 사람에게 물을 UI가 없습니다.
검증됨 / 검증되지 않음
세 개의 spike, 각각 자체 app-server(임시 포트)를 시작하며, 현재 보고 있는 thread에 영향을 미치지 않습니다:
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에
user message로 렌더링되고, Codex가 정상적으로 응답하며, 응답 스트림이 Claude Code로 돌아옵니다.
확인됨 (codex-cli 0.147.0):
알림은 항상
threadId를 포함하며, 스트림 유형(item/agentMessage/delta)은turnId도 포함하고,turn/start가 반환하는turn.id와 스트림의 그것이 완전히 일치합니다. 초기에는 '알림에 threadId가 없다'고 생각했지만, 실제로는 클라이언트가 thread에 join하지 않은 증상이었습니다.thread는 먼저
thread/resume으로 join해야 알림을 받을 수 있습니다; 그리고 thread는 첫 대화가 완료되어야 resumable 상태가 됩니다.historyMode: "paginated"(TUI가 생성한 thread) →thread/read에includeTurns를 사용하면 실패합니다(list_turns is not supported yet). 사후에 history를 가져오는 이 경로는 현재 막혀 있습니다; 응답은 실시간 스트림으로 받습니다.승인은 모든 클라이언트에 브로드캐스트되며, 먼저 응답한 클라이언트의 응답이 적용됩니다(macOS 확정). 침묵하는 클라이언트도 동일한 요청을 받고, 나중에
serverRequest/resolved를 수신하며, turn이 멈추지 않습니다. OS sandbox helper를 시작할 수 없는 기기(일부 관리되는 기업 Windows에서ShellExecuteExW failed to launch setup helper: 1223발생)에서는 파일 쓰기가 '사람에게 묻기' 전에 실패하여 승인 요청이 전혀 실행되지 않으므로, 해당 항목들은 SKIP으로 표시됩니다 — 환경 제한, 프로토콜 문제가 아닙니다.사용자 수준의
PermissionRequesthook은 승인 요청을 완전히 가로챕니다, 클라이언트는 하나도 받지 못합니다 (macOS 실험 확인). 자세한 내용은 위 '승인' 섹션을 참조하세요.
아직 검증되지 않음:
turn/steer(실행 중인 turn에 끼어들기)는 schema만 읽었고, 실제 테스트되지 않음.-C를 실제로 실행하지 않음→ 실제 테스트 완료: TUI가-C없이 연결되면 thread의 cwd는 app-server의 cwd이며, 당신이 어느 디렉터리에서codex --remote를 입력했는지와 무관합니다;-C <dir>을 사용하면 해당 디렉터리로 변경됩니다. (macOS, pty로 TUI를 시작하고 다른 클라이언트가thread/read를 읽음.)전체 프로토콜은
[experimental]로 표시되어 있으며, codex 버전이 업그레이드되면 변경될 수 있습니다.
참고
반대 방향이 Stop hook인 이유, 다른 메커니즘은 아닌 이유:
docs/reverse-channel.md
프로토콜 schema: 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