
Discord에서 Codex를 이어 쓰는 로컬 브리지 만들기
Codex를 VS Code에서 쓰다 보면 자리를 옮겼을 때 짧은 지시만 Discord로 이어 보내고 싶을 때가 있다. 핵심 요구사항은 거창하지 않다. 외부에 포트를 열지 않고, Discord의 특정 채널에 쓴 메시지를 현재 Codex 세션으로 전달하고, Codex의 응답을 다시 Discord에서 확인하는 정도면 충분하다.
이 글은 그런 요구를 로컬 브리지로 구현한 과정을 정리한다. 최종 구조는 두 단계로 나뉜다. 먼저 안정적인 기본 경로로 codex exec resume을 붙였고, 그 다음 VS Code에 열려 있는 Codex 패널까지 같이 갱신되도록 설치된 VS Code 확장에 작은 inbox poller를 추가했다.
처음 목표는 외부 노출 없이 이어 쓰기였다
처음부터 웹훅 서버를 공개하거나 터널을 열 필요는 없었다. Discord 봇은 원래 Discord Gateway에 outbound 연결을 유지한다. 따라서 사용자가 Discord에 메시지를 쓰면 봇은 이미 열려 있는 Gateway 이벤트로 메시지를 받을 수 있다. 로컬 머신 쪽에 inbound 포트를 열 필요가 없다.
이 구조에서는 Codex 전용 Discord 채널 하나를 만들고, 그 채널 안에서만 동작하게 제한하는 편이 안전하다. 코드, 파일 경로, 명령 출력, 작업 메모가 대화에 포함될 수 있기 때문에 일반 알림 채널과 분리하는 것이 좋다.
기본 경로: codex exec resume
가장 먼저 붙인 방식은 단순하다. Discord 봇이 최근 Codex 세션 목록을 읽어 드롭다운으로 보여주고, 사용자가 세션을 선택하면 그 세션 ID를 저장한다. 이후 같은 채널에 일반 메시지를 쓰면 봇이 다음 형태로 Codex를 실행한다.
codex exec resume --json --skip-git-repo-check \
--output-last-message /tmp/last-message.txt \
<session_id> "<discord message>"
응답은 --output-last-message 파일에서 읽어 Discord에 다시 보낸다. 동시에 봇은 ~/.codex 아래의 세션 JSONL 로그를 polling해서 사용자 메시지와 Codex 응답을 Discord에 backfill하거나 mirror한다.
이 방식은 안정적이다. Codex CLI가 제공하는 공식 실행 경로를 쓰고, Discord 봇도 로컬 파일과 Gateway만 사용한다. 다만 한 가지 한계가 있었다. 이렇게 추가된 턴은 세션 파일에는 남지만, 이미 열려 있는 VS Code Codex 패널이 그 변경을 즉시 live-tail하지는 않았다.
왜 VS Code 패널에는 바로 안 보였나?
설치된 Codex VS Code 확장을 열어보면 구조가 조금 다르다. 확장은 내부에서 codex app-server를 자식 프로세스로 띄우고, webview는 vscode://codex/... 브리지를 통해 그 app-server와 통신한다. 열린 패널의 실시간 화면은 단순히 JSONL 파일을 tail하는 것이 아니라, extension host와 app-server 사이의 notification stream으로 갱신된다.
그래서 외부에서 codex exec resume으로 같은 세션 파일에 기록해도, 현재 열려 있는 VS Code webview에 새 이벤트가 자동으로 broadcast되지는 않는다. 세션 지속성과 현재 UI 스트림은 서로 다른 문제였던 셈이다.
실험 경로: VS Code 확장 inbox poller
열려 있는 VS Code Codex 패널까지 같이 갱신하려면, 같은 extension-owned app-server에 턴을 넣어야 했다. 이를 위해 설치된 Codex VS Code 확장 번들에 아주 작은 hook을 추가했다. Discord 봇은 더 이상 바로 Codex CLI를 실행하지 않고, 로컬 inbox JSONL 파일에 메시지를 한 줄 추가한다.
{
"id": "timestamp-random",
"createdAt": "2026-05-05T13:50:00.000Z",
"threadId": "019df81f-7552-7273-9efc-d5376b66f468",
"text": "Discord에서 사용자가 보낸 메시지입니다...",
"source": "discord"
}
패치된 VS Code 확장은 이 inbox를 1초마다 읽고, 처리하지 않은 줄을 app-server로 전달한다. 내부 호출 순서는 다음과 같다.
thread/resume으로 선택한 세션을 app-server에 올린다.turn/start로 Discord 메시지를 새 사용자 입력으로 보낸다.- 처리 offset을
runtime/codex-vscode-inbox.jsonl.offset에 기록한다. - 성공과 실패는
runtime/codex-vscode-inbox.log에 남긴다.
이미 해당 thread가 app-server에서 활성 상태라면 thread/resume이 실패할 수 있다. 이 경우에도 hook은 실패를 로그에 남기고 turn/start를 시도한다. 실제로 열린 VS Code 패널에 Discord 메시지가 들어오는지 확인하는 데는 이 fallback이 중요했다.
Discord 쪽 사용자 경험
Discord 채널에서는 !codex 명령으로 세션 선택 패널을 띄운다. Discord의 select menu를 사용하므로 긴 세션 ID를 직접 입력할 필요가 없다. 세션을 선택하면 최근 대화 일부를 채널에 backfill하고, 이후 일반 메시지는 선택된 Codex 세션으로 전달된다.
VS Code bridge 모드가 켜져 있으면 봇은 메시지를 받은 직후 다음처럼 응답한다.
@user VS Code Codex에 전달했습니다.
그 뒤 실제 Codex 응답은 VS Code 패널에서 진행되고, Discord 쪽 mirror는 세션 로그 polling을 통해 따라온다. 즉 Discord는 원격 입력 장치이자 보조 모니터가 되고, VS Code는 여전히 실제 작업 화면으로 남는다.
여러 채널로 동시에 작업하기
처음 구현은 Codex 전용 채널 하나만 바라봤다. 실제로 써보니 작업 주제가 갈라질 때는 채널을 여러 개 두는 편이 훨씬 낫다. 예를 들어 한 채널은 코딩 작업 세션에 묶고, 다른 채널은 리뷰 작업 세션에 묶어두는 식이다.
이를 위해 브리지 상태를 전역 세션 하나가 아니라 채널별 상태 맵으로 바꿨다. 각 Discord 채널은 자신만의 선택된 Codex 세션, JSONL mirror offset, CLI fallback 실행 상태를 가진다. 그래서 두 채널이 서로 다른 Codex 세션에 연결되어도 polling 위치가 섞이지 않고, 한 채널에서 Codex가 처리 중이어도 다른 채널의 작업을 막지 않는다.
DISCORD_CODEX_CHANNEL_ID=첫번째_전용_채널_ID
DISCORD_CODEX_CHANNEL_IDS=두번째_채널_ID,세번째_채널_ID
기존 단일 채널 설정은 그대로 유지했다. 추가 채널이 필요할 때만 DISCORD_CODEX_CHANNEL_IDS에 쉼표로 더 넣으면 된다. 각 채널에서 !codex를 한 번씩 실행해 원하는 세션을 선택하면 그 다음부터는 채널 단위로 독립적으로 이어 쓸 수 있다.
운영 파일과 설정
브리지에 사용한 주요 환경 변수는 다음과 같다.
DISCORD_CODEX_CHANNEL_ID=전용_채널_ID
DISCORD_CODEX_CHANNEL_IDS=추가_채널_ID,또_다른_채널_ID
DISCORD_CODEX_ENABLED=1
DISCORD_CODEX_VSCODE_BRIDGE=1
DISCORD_CODEX_VSCODE_INBOX=/Users/awe/Desktop/servers/discord/runtime/codex-vscode-inbox.jsonl
DISCORD_CODEX_SESSION_LIMIT=10
DISCORD_CODEX_POLL_INTERVAL_MS=3000
DISCORD_CODEX_BACKFILL_LIMIT=12
VS Code 확장 패치는 별도 스크립트로 재적용할 수 있게 했다.
node ./servers/discord/scripts/patch-codex-vscode-bridge.js
이 스크립트는 설치된 확장 파일을 수정하기 전에 백업을 만든다. 확장 업데이트로 패치가 사라지면 스크립트를 다시 실행하고 VS Code를 reload하면 된다.
보안상 주의할 점
이 구성은 외부 inbound 포트를 열지 않는다. Discord 입력은 Gateway 연결로 들어오고, Codex 전달은 로컬 파일과 로컬 VS Code extension host 안에서 처리된다. 그래도 안전하다고 방심하면 안 된다.
- Codex 전용 채널은 반드시 비공개로 둔다.
- 봇은 설정된 채널 ID에서만 Codex 메시지를 처리한다.
- Discord로 보내는 mirror에는 token, password, secret 계열 문자열을 마스킹한다.
- 긴 응답은 Discord 전송 전에 잘라낸다.
- VS Code 확장 패치는 공식 API가 아니므로 업데이트 후 재검증한다.
특히 마지막 항목이 중요하다. 설치된 확장 번들을 직접 패치하는 방식은 실험으로는 유용하지만, 장기적으로는 깨질 수 있다. 버전이 올라가면 내부 함수 이름이나 코드 위치가 바뀔 수 있으므로 재패치 스크립트와 rollback 절차를 같이 보관해야 한다.
문제가 생겼을 때 확인할 로그
Discord 봇이 bridge 모드로 떠 있는지는 PM2 로그에서 확인할 수 있다.
pm2 logs discord --lines 80 --nostream
VS Code extension hook이 inbox를 읽고 있는지는 다음 로그를 보면 된다.
tail -80 ./servers/discord/runtime/codex-vscode-inbox.log
정상 시작 시에는 inbox 경로가 기록되고, 메시지를 처리하면 sent 로그가 남는다. 실패하면 resume skipped 또는 failed 로그가 남기 때문에 어느 단계에서 막혔는지 추적할 수 있다.
정리
처음에는 Discord 메시지를 codex exec resume으로 전달하는 것만으로 충분해 보였다. 하지만 VS Code에 열린 현재 Codex 패널까지 자연스럽게 이어 쓰려면, 세션 파일에 쓰는 것과 live UI stream에 넣는 것은 다르다는 점을 분리해서 봐야 했다.
최종적으로는 기본 CLI 경로를 fallback으로 남기고, 실험용 VS Code extension inbox bridge를 추가했다. 이후 채널별 상태를 분리해 여러 Discord 채널에서 서로 다른 Codex 세션을 동시에 이어 쓸 수 있게 했다. 이 방식은 공식 확장 API가 아니므로 유지보수 리스크가 있지만, 로컬 전용 환경에서 Discord를 Codex 입력 장치로 쓰기에는 꽤 실용적이다. 중요한 것은 외부 포트를 열지 않고도, 선택형 UI와 세션 backfill, VS Code 패널 반영까지 한 흐름으로 묶을 수 있다는 점이다.
'프로그래밍 > C, C++, Java, Python' 카테고리의 다른 글
| 윈도우 소리를 맥북 스피커로 보내기: FFmpeg와 VB-CABLE로 만든 개인용 네트워크 스피커 (0) | 2026.05.07 |
|---|---|
| Python free-threaded 빌드는 무엇이 달라졌을까? 3.13~3.14 기준 설치·확인·제약 정리 (1) | 2026.05.07 |
| 삼성 SW 역량테스트 B형/Pro 대비 - 최적화 기법 (0) | 2021.03.30 |
| Modbus TCP 통신을 위한 프로토콜 파헤치기 & 예제 코드 (0) | 2021.02.02 |
| [Python] 파이썬 Socket.IO 오류 unexpected response code 400 (0) | 2021.01.16 |





