Skip to content

[Bug][Codex] macOS Codex 0.152.0 streams disconnect through ocx 2.39.0 #3245

Description

@Vontean

Client or integration

Codex CLI / native Codex integration through OpenCodex

Area

Streaming / Responses compatibility

Summary

On macOS, Codex CLI 0.152.0 can complete a first-party request when using the native OpenAI route, but the same minimal request repeatedly fails when Codex is routed through OpenCodex 2.39.0 at http://127.0.0.1:10100/v1.

Both a first-party model and a configured third-party model fail with:

stream disconnected before completion: error sending request for url (http://127.0.0.1:10100/v1/responses)

The failure reproduces with the launchd service and with the documented foreground A/B command, so it does not appear to be only a launchd proxy-environment inheritance issue.

Environment

  • OS: macOS 26.6.2, Darwin 25.6.0 arm64
  • OpenCodex: 2.39.0
  • Codex CLI: 0.152.0
  • OpenCodex runtime: bundled Bun 1.4.0
  • Local upstream HTTP proxy: http://127.0.0.1:1082
  • Codex native integration: enabled with ocx integration native codex on
  • OpenCodex websockets: false

Setup

ocx config set proxy 'http://127.0.0.1:1082'
ocx config validate
ocx service repair
ocx integration native codex on
ocx ready --wait --timeout 60

All three provider checks succeeded:

openai: connected
command-code: connected (62 models available)
zai: connected (10 models available)

ocx doctor reports Configured proxy: set, routing=opencodex-local, and Bun 1.4.0.

Reproduction

First-party:

codex exec --ephemeral --sandbox read-only --skip-git-repo-check --json \
  -m gpt-5.6-luna \
  'Reply with exactly OK and do not use any tools.'

Third-party:

codex exec --ephemeral --sandbox read-only --skip-git-repo-check --json \
  -m command-code/deepseek-deepseek-v4-flash \
  'Reply with exactly OK and do not use any tools.'

For both requests Codex first reports the expected client-facing WebSocket fallback:

failed to connect to websocket: HTTP error: 426 Upgrade Required
falling back to HTTP

Then it retries five times and ends with:

stream disconnected before completion: error sending request for url (http://127.0.0.1:10100/v1/responses)
turn.failed

The same failure reproduces after restoring the launchd service and also with:

ocx service stop
HTTP_PROXY=http://127.0.0.1:1082 \
HTTPS_PROXY=http://127.0.0.1:1082 \
ocx start

Controls

  • Before enabling native Codex routing, the same first-party codex exec request completed natively with OK and turn.completed.
  • The upstream proxy is reachable: a proxied request to https://chatgpt.com/backend-api/wham/usage returned HTTP 401 quickly, which confirms the connection path without exposing credentials.
  • Direct local POST /v1/chat/completions with zai/glm-5.3 and stream: true completed with data: [DONE].
  • Direct local minimal POST /v1/responses with zai/glm-5.3 and stream: true completed with response.completed and data: [DONE].

Therefore provider authentication, the local proxy, the upstream HTTP proxy, and minimal local SSE delivery all work. The failure seems specific to the full request shape emitted by Codex CLI and its Responses bridge/stream relay path.

Expected behavior

Codex should receive a complete streamed response and a terminal Responses event for both the first-party and third-party model requests.

Actual behavior

The local /v1/responses connection is closed before Codex receives a terminal event. Codex retries five times and reports stream disconnected.

Related issues

This appears related to the historical Responses/macOS streaming compatibility issues in #1127 and #1809, but the current reproduction is on 2.39.0 with Codex 0.152.0 and also affects a minimal first-party request. #324 documents the expected 426 behavior when the client-facing WebSocket endpoint is disabled.

Could you please investigate the Codex 0.152.0 request shape and the OpenCodex Responses bridge/stream relay on macOS? A debug classification of whether the close occurs before or after upstream response.created would also help distinguish request translation from terminal-event delivery.

No credentials or tokens are included in this report. Bun was kept at 1.4.0; no runtime downgrade was attempted.

Activity

  1. added
    bugSomething isn't working
    cliCLI, config inject, packaging flags
    platformOS/service/tray/ACL (Windows-heavy, not Windows-only)
    serviceService lifecycle (WinSW/launchd/scheduler)
    streamingSSE, WebSocket, terminal stream frames
    on Sep 2, 2026
  2. lidge-jun commented on Sep 2, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 66 / 80

    이 이슈는 macOS에서 Codex CLI 0.152.0이 네이티브 OpenAI 경로로는 같은 최소 프롬프트가 성공하는데, OpenCodex 2.39.0(http://127.0.0.1:10100/v1)으로 라우팅하면 1차 모델과 3차 모델 모두 stream disconnected before completion: error sending request for url (http://127.0.0.1:10100/v1/responses) 로 다섯 번 재시도 후 turn.failed 한다는 보고입니다. 지금 로컬 dev HEAD는 7d25f9966 (docs(cursor): dashboard section and read-only surface note (#3249), package 2.40.0) 입니다. 보고 버전(2.39.0)과 HEAD(2.40.0) 사이에 Responses 쪽으로는 업스트림 WS 옵트인(#3216), 아웃바운드 body ceiling(#3196), spark namespace scrub(#3224/#3234), combo zero-output failover(#3236) 등이 들어갔지만, Darwin tee 기본 정책 자체를 바꾼 커밋은 없습니다.

    보고에 나온 426 → HTTP 폴백은 지금 코드에서도 정상 동작입니다. src/server/index.ts 에서 /v1/responses WebSocket 업그레이드가 오면 websocketsEnabled(config)가 꺼져 있을 때 426 upgrade_required를 돌려 줍니다. 주석대로 Codex는 이걸 세션 범위 HTTP 폴백으로 매핑합니다. 닫힌 #324가 기대한 동작이고, 보고자도 websockets: false 라고 적었습니다. 따라서 426 자체는 버그가 아닙니다. 진짜 문제는 폴백 뒤 HTTP SSE가 끝나기 전에 클라이언트 쪽 연결이 끊기는 부분입니다.

    문서 docs-site/src/content/docs/guides/codex-integration.md Proxy connection errors 절은 같은 문자열을 “프록시가 안 떠 있음”으로 안내합니다. 그런데 이 보고의 대조 실험은 그 설명을 이미 반박합니다. 같은 머신에서 직접 POST /v1/chat/completions·POST /v1/responses (zai, stream)가 [DONE]/response.completed까지 가고, 네이티브(주입 전) Codex exec도 성공합니다. launchd와 ocx start 포그라운드 A/B에서도 재현되므로 launchd 환경 상속만의 문제로 보기 어렵습니다. 보고자 말대로 Codex CLI가 보내는 전체 Responses 요청 모양과 브리지/릴레이 경로를 의심하는 게 맞습니다.

    역사적으로 가까운 이슈는 #1127(Darwin에서 rewrite/tee 경로가 클라이언트로 SSE를 못 넘기던 문제, #1142로 랜딩·닫힘)과 #1809(커스텀 프로바이더용 terminal repair 탈출구, 관련 PR #2362는 아직 open)입니다. 지금 HEAD의 Darwin 정책은 src/lib/bun-stream-caps.ts selectEagerPath 기준입니다. Windows는 no-rewrite면 eager 가능, Darwin은 streamMode: "eager-relay" 명시 opt-in일 때만 eager이고 auto는 계속 tee입니다. src/server/responses/core.ts 주석도 같은 말을 합니다. #1127 증상은 “업스트림 200인데 클라이언트가 이벤트 0개 + hang”에 가깝고, 이번 보고는 “재시도 후 stream disconnected / error sending request”라서 완전히 같은 버그라고 단정하면 안 됩니다. 다만 macOS + Bun 1.4.0 + Responses passthrough라는 같은 무대 위에서 일어나는 일이라 조사 출발점으로 쓸 만합니다.

    추가로 보고 환경에는 아웃바운드 프록시 http://127.0.0.1:1082 가 ocx config set proxy 로 켜져 있습니다. OpenCodex 쪽은 루프백을 NO_PROXY에 남기는 설계(src/types/config.ts / src/lib/provider-outbound.ts)이고, chatgpt.com usage 401 확인도 있어서 “업스트림 프록시가 완전 죽음”은 아닙니다. 그래도 Codex CLI 프로세스 자체에 HTTP_PROXY/HTTPS_PROXY가 새어 들어가면, 어떤 요청은 127.0.0.1:10100을 프록시로 우회하려다 error sending request 형태로 깨질 수 있습니다. 직접 curl은 프록시 없이 성공하고 Codex만 실패하는 패턴과 잘 맞습니다. 먼저 Codex 쪽 환경에서 루프백 NO_PROXY와 프록시 상속을 가르는 실험이 필요합니다.

    경로 src/server/index.ts WS 426 게이트 - 보고의 426 폴백과 일치. 버그 후보 아님.
    경로 src/lib/bun-stream-caps.ts selectEagerPath - Darwin auto는 tee. 재현 환경에서 streamMode: "eager-relay" 한 번 켜 보면 경로 분기 단서가 됩니다.
    경로 src/server/responses/core.ts passthrough SSE tee/eager 분기 - Codex 풀 요청이 rewrite를 켜면 Darwin에서 tee+JS-pull로 갈 수 있음. #1127 계열과 겹치는지 request-log로 확인 필요.
    경로 src/server/responses/ws-upstream.ts / #3216 - 업스트림 WS는 옵트인. 보고는 클라이언트면 WS 비활성. 원인 후보 우선순위는 낮음.
    환경 Codex CLI vs 최소 curl - 같은 포트에서 curl Responses는 성공, Codex 모양만 실패. 본문·헤더·세션·도구 스키마 차이가 핵심 가설.
    버전 갭 2.39.0 → HEAD 2.40.0 - 즉시 Darwin 스트림 수정은 없음. 그래도 재현을 2.40.0/dev에서 한 번 더 확인하면 이미 고친 인접 회귀인지 가릴 수 있음.

    메인테이너의 판단이 필요한 지점

    너의 추천
    닫지 말고 조사 이슈로 유지한다. types.ts/config.ts 분할과 무관하고 중복 클로즈 대상도 아니다. 다음 구체 스텝: (1) 같은 재현을 2.40.0에서 한 번 더, (2) Codex 실행 환경에서 HTTP_PROXY/NO_PROXY를 비운 A/B, (3) OpenCodex request-log에서 해당 턴의 status·첫/끝 SSE 이벤트·upstream 상태 확인, (4) 가능하면 streamMode: "eager-relay" A/B. 이 네 결과가 오기 전에는 코드 수정 PR을 열지 말고 needs-info로 분류 데이터를 받는 편이 안전하다.

    이 댓글은 grok-bot이 작성했습니다

  3. Vontean commented on Sep 2, 2026

    @Vontean
    Author

    I found a local workaround and some additional transport-level evidence.

    On the affected setup (Codex 0.152.0 / OpenCodex 2.39.0 / bundled Bun 1.4.0 / macOS 26.6.2), enable the OpenCodex Responses WebSocket endpoint:

    ocx config set websockets true
    ocx service repair
    ocx ready --wait --timeout 60

    After this change, both of these full Codex requests completed on the first attempt with OK and turn.completed:

    • gpt-5.6-luna
    • command-code/deepseek-deepseek-v4-flash (about 162k input tokens)

    Before enabling WebSockets, I inserted a localhost transport probe between Codex and OpenCodex. It observed the WebSocket upgrade request and OpenCodex's expected HTTP 426 response. Codex then logged falling back to HTTP, but in this environment the probe observed no subsequent HTTP POST before Codex reported stream disconnected before completion. The failed attempts also did not appear as /v1/responses requests in the OpenCodex usage log.

    This suggests the failure may be in the Codex 0.152.0 426-to-HTTP fallback path, rather than necessarily in the OpenCodex Responses bridge. This is an observation from one environment, not yet a confirmed upstream root cause.

    I also tested the following without success and reverted them:

    • streamMode=eager-relay
    • forcing the OpenAI provider to HTTP/1.1
    • disabling Codex zstd request compression
    • disabling the Bun request timeout earlier in the route

    Rollback:

    ocx config set websockets false

    Enabling WebSockets is a temporary workaround until the 426 fallback behavior is fixed or further explained.

  4. Ingwannu commented on Sep 2, 2026

    @Ingwannu
    Owner

    The new localhost-probe result materially narrows the boundary. If the probe received the WebSocket upgrade and OpenCodex's 426, but received no subsequent HTTP POST /v1/responses and OpenCodex recorded no request, the failing HTTP attempt never entered the OpenCodex data plane. That rules out the OCX SSE relay as the cause of this specific reproduced attempt.

    I checked the released Codex source. In rust-v0.152.0, codex-rs/core/src/client.rs recognizes HTTP 426 as FallbackToHttp and is then supposed to call stream_responses_api immediately. The stable rust-v0.152.1 release has the same fallback code, and its three release commits do not change this client path. The observation therefore points to a Codex-side failure between selecting fallback and issuing the HTTP request, not to a known 0.152.1 fix.

    OpenCodex should not work around this by accepting WebSocket upgrades when websockets: false; that would violate the operator's explicit transport setting. Enabling websockets: true is a valid opt-in workaround for this environment because it avoids the broken fallback path.

    Please retest the same probe on Codex 0.152.1. If no HTTP POST appears there either, the next actionable step is an upstream openai/codex report containing the minimal 426-recording probe and these three observations: the upgrade receives 426, Codex logs falling back to HTTP, and no HTTP request reaches the probe. Link that report here. We should keep this issue open as upstream-tracking/needs-info rather than patching the OpenCodex relay without an incoming request.

  5. added
    needs-infoWaiting on reporter for a concrete spec or reproduction
    upstream-trackingBlocked on an upstream (Codex CLI/Desktop) fix; kept open for discoverability
    on Sep 2, 2026
  6. lidge-jun commented on Sep 2, 2026

    @lidge-jun
    Owner

    Investigated this against the current dev tree. Reporting what the code shows, because it moves the question off OpenCodex and into the Codex client's fallback path.

    The 426 is deliberate. src/server/index.ts:1107-1126 returns 426 when WebSockets are disabled. The Responses data plane only begins on the subsequent POST (src/server/index.ts:1755-1787).

    Your probe shows no POST and no usage-log entry. That matters: it means the SSE relay, terminal repair, idle timeout, and outbound connection reuse were never reached at all. Those live at src/server/responses/core.ts:4657-4675 and src/lib/upstream-retry.ts:294-311, and none of them can be responsible for a disconnect that happens before the first POST.

    The 426 → HTTP fallback is Codex-side and already covered upstream. codex-rs/core/src/client.rs:2048-2064 routes 426 to HTTP, and codex-rs/core/tests/suite/websocket_fallback.rs:34-80 asserts exactly one resulting POST.

    Our control test predates 2.39.0 and passes. tests/server-auth.test.ts:1384-1422 asserts 426 followed by HTTP 200.

    So there is no OpenCodex-side change that would be honest to make here. What would settle it: re-run on Codex 0.152.1 or later and record HTTP_PROXY, HTTPS_PROXY, NO_PROXY, and the localhost probe alongside the failure. If the HTTP POST is still absent with those recorded, the fix belongs in the upstream client's fallback (fresh request, proxy bypass, session state), and this issue is the right place to track it.

    Keeping upstream-tracking and needs-info.

  7. lidge-jun commented on Sep 3, 2026

    @lidge-jun
    Owner

    @Vontean — following up during a backlog review. This is not closed; it needs one piece of information from you to move.

    Where this stands. OpenCodex returns 426 deliberately at src/server/index.ts and the HTTP fallback path is covered by tests/server-auth.test.ts. Your own probe showed no subsequent POST, which places the failure upstream in the Codex client rather than in the proxy.

    What would unblock it. Please add whether this still reproduces on a current Codex build and a current ocx — and if so, an ocx logs --jsonl excerpt spanning the disconnect.

    If that arrives, this gets picked up in the next triage pass. Without it, the report cannot be distinguished from configurations that already work, so it will keep sitting here rather than being wrong on purpose.

  8. lidge-jun commented on Sep 4, 2026

    @lidge-jun
    Owner

    Following up during the 260904 backlog review. Still open, still needs-info, and the ask has not changed — but the version gap has widened enough to be worth stating plainly.

    This was filed against OpenCodex 2.39.0. dev is now on the 2.43.0 line, and several things in the streaming and Responses path have moved since, including work on mid-stream socket handling. A "stream disconnected before completion" against a build that far back cannot be attributed to current code, and I would rather not guess at which of the intervening changes is relevant.

    The single most useful thing you can do is re-test on current: does the same minimal request still fail through OpenCodex 2.42.x or later, with the same Codex CLI version? If it passes, say so and I will close this with that as the record. If it still fails, the same A/B you already ran — but on the new version — tells us it is a live defect rather than a fixed one, and I will treat it as such.

    If it is easier, ocx debug provider on around one failing turn plus the resulting run-request entry would be more informative than the client-side error, which only reports that the socket ended.

    The upstream-tracking label is on this because a Codex CLI change is a plausible cause and we do not control that side. If there is no reply, stale-needs-info will eventually close it, which is the honest outcome for a report we cannot reproduce or attribute — not a judgment that it was invalid.

  9. lidge-jun commented on Sep 4, 2026

    @lidge-jun
    Owner

    Still blocked on reporter evidence, and the code-side search is exhausted rather than merely unfinished.

    The decisive detail is in your own probe: there was no POST and no usage-log entry. The Responses data plane does not start until the subsequent POST (src/server/index.ts:1755-1787), so the SSE relay (src/server/responses/core.ts:4657-4675), terminal repair, idle timeout, and outbound connection reuse (src/lib/upstream-retry.ts:294-311) were never reached. None of them can explain a disconnect that happens before the first POST.

    The 426 is intentional — src/server/index.ts:1107-1126 returns it when WebSockets are disabled, and you reported websockets: false. The 426-then-HTTP fallback is client-side and covered upstream (codex-rs/core/src/client.rs:2048-2064), and our side of that contract is green: tests/server-auth.test.ts:1384-1422 asserts 426 followed by HTTP 200.

    The version gap has also widened — you reported 2.39.0 and dev is now 2.43.0. Several streaming and Responses changes landed in between, though none altered the Darwin tee policy itself.

    Guessing at a fix from here would produce a change nobody could prove fixed anything. What would unblock it:

    1. Does the same failure reproduce on 2.42.x or later?
    2. HTTP_PROXY / HTTPS_PROXY / NO_PROXY as set in the failing shell.
    3. A run-request entry with ocx debug provider on, or an ocx logs --jsonl excerpt covering the failing turn — specifically whether a POST leaves the client at all.

    Item 3 is the one that matters most: it distinguishes "the proxy mishandled the request" from "the request never arrived", and right now the evidence points at the second.

  10. lidge-jun commented on Sep 8, 2026

    @lidge-jun
    Owner

    Closing as stale needs-info. This was filed against 2.39.0 and dev is now on the 2.49.0 line, with
    substantial streaming and Responses changes in between, so a disconnect on that build cannot be
    attributed to current code.

    Your own transport probe is what makes this the honest outcome rather than a guess. The upgrade
    received the deliberate 426, Codex logged falling back to HTTP, and no subsequent
    POST /v1/responses reached the probe or the usage log. The OpenCodex Responses data plane does not
    begin until that POST, so the SSE relay, terminal repair, idle timeout, and outbound connection reuse
    were never reached and cannot explain the failure. The 426 to HTTP fallback is client-side, and our
    half of the contract is covered by a test asserting 426 followed by HTTP 200.

    ocx config set websockets true remains a valid opt-in for this environment.

    If it still reproduces on a current Codex CLI and a current ocx, please reopen with an
    ocx logs --jsonl excerpt spanning the disconnect, or a run-request entry captured with
    ocx debug provider on — specifically whether a POST leaves the client at all. Thank you for the
    localhost probe; it is the single most useful piece of evidence in this thread.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcliCLI, config inject, packaging flagsneeds-infoWaiting on reporter for a concrete spec or reproductionplatformOS/service/tray/ACL (Windows-heavy, not Windows-only)serviceService lifecycle (WinSW/launchd/scheduler)streamingSSE, WebSocket, terminal stream framesupstream-trackingBlocked on an upstream (Codex CLI/Desktop) fix; kept open for discoverability

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions