Responses WebSocket 101 response does not include x-codex-turn-state despite client replay support
What issue are you seeing?
Current Codex source still models x-codex-turn-state as a per-turn sticky-routing token that is learned from the server and replayed for subsequent requests in the same turn. However, a direct Responses WebSocket upgrade to the Codex upstream endpoint succeeds with 101 Switching Protocols but does not include x-codex-turn-state in the handshake response.
This is not a generic WebSocket connection failure: the upgrade succeeds and other upstream response headers are visible. In the same 101 response, x-models-etag and x-openai-proxy-wasm are present, but x-codex-turn-state is absent.
This makes the expected reconnect/retry behavior unclear. If the original WebSocket is lost during a turn, the client cannot replay a turn-state token that was never returned.
Relevant current source references from origin/main at ccdfb4f342:
codex-rs/core/src/client.rsdescribesx-codex-turn-stateas a sticky-routing token received on turn start and replayed for subsequent requests in the same turn.codex-rs/codex-api/src/endpoint/responses_websocket.rsattempts to capturex-codex-turn-statefrom the WebSocket handshake response header, and also fromresponse.metadataevent headers.codex-rs/codex-api/src/sse/responses.rshas the analogous HTTP/SSE response-header andresponse.metadatacapture path.
What steps can reproduce the bug?
- Use a valid ChatGPT-auth Codex account.
- Open a direct TLS socket to
chatgpt.comand send a WebSocket upgrade request to/backend-api/codex/responses. - Include the current Responses WebSocket beta header and turn metadata. Sensitive values below are redacted:
GET /backend-api/codex/responses HTTP/1.1
Host: chatgpt.com
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: <redacted>
Authorization: Bearer <redacted>
ChatGPT-Account-ID: <redacted>
OpenAI-Beta: responses_websockets=2026-02-06
Version: 0.142.3
User-Agent: codex-tui/0.142.3 (Ubuntu 24.04; x86_64) unknown (codex-tui; 0.142.3)
Originator: codex-tui
session_id: <redacted-uuid>
thread_id: <redacted-uuid>
session-id: <redacted-uuid>
thread-id: <redacted-uuid>
x-client-request-id: <redacted-uuid>
x-codex-window-id: <redacted-uuid>
x-codex-turn-metadata: <redacted-json>
Actual response summary from a live probe on 2026-06-29:
{
"ok": true,
"status": "HTTP/1.1 101 Switching Protocols",
"request_beta": "responses_websockets=2026-02-06",
"request_version": "0.142.3",
"request_turn_metadata_present": true,
"response_x_codex_turn_state_present": false,
"response_x_models_etag_present": true,
"response_x_openai_proxy_wasm_present": true
}
I also inspected WebSocket event frames for normal response.create traffic. The observed stream contained normal response lifecycle events, but no response.metadata event carrying headers["x-codex-turn-state"].
What is the expected behavior?
One of these should be true:
- If
x-codex-turn-stateis still part of the protocol contract, the upstream should return it either in the initial WebSocket 101 response header or in a documentedresponse.metadata.headers["x-codex-turn-state"]event early enough for same-turn reconnects/retries. - If this token has been deprecated, moved to another field, or replaced by a different sticky-routing mechanism, the client code/comments/tests should be updated to document the new expected behavior.
Additional information
I am filing this separately from generic WebSocket disconnect issues because the repro is specifically about the missing turn-state token. The connection itself upgrades successfully, and other response headers are observable, so this does not look like local header stripping.
For privacy, I redacted auth/account/session/thread identifiers and did not include codex doctor --json output because my local setup includes unrelated custom provider configuration. The protocol probe above was direct to chatgpt.com, not through that custom provider.
2 Comments
I checked current
main(80f54d1) and the client still treatsx-codex-turn-stateas an active same-turn routing contract.Relevant paths:
codex-rs/core/src/client.rsdocuments the token as learned on turn start and replayed for same-turn requests.codex-rs/codex-api/src/endpoint/responses_websocket.rscaptures it from the WebSocket 101 response header and fromresponse.metadataevent headers.codex-rs/codex-api/src/sse/responses.rshas the analogous SSE metadata/header parser.codex-rs/core/tests/suite/turn_state.rsincludes WebSocket tests whereresponse.metadataseeds the token.So if production WebSocket no longer returns either the 101 header or a metadata event, the client contract/test fixture is stale even if ordinary single-connection turns still work.
Suggested acceptance criteria:
turn_statetests accordinglyGentle follow-up on this.
This is still hard to diagnose from the client side because the current WebSocket reachability check can report that the Responses WebSocket upgrade works, while not showing whether the same-turn sticky-routing token was actually returned.
I prepared a minimal diagnostic branch here:
https://github.com/openai/codex/compare/main...lyd123qw2008:codex:lyd/doctor-turn-state-diagnostic
It only adds a
turn_state_header_presentfield to the Responses WebSocket probe and reportsturn state header present: true/falseincodex doctor. It does not change runtime behavior or fail the doctor check when the header is missing.Could someone confirm the intended current contract?
x-codex-turn-statestill expected to be returned via the WS 101 response header orresponse.metadatafor same-turn reconnect/sticky routing?That clarification would make it easier to know whether the client tests/docs should be updated, or whether this is an upstream routing/header emission regression.