WebSocket fallback should recover after transient transport failures
What problem are you seeing?
When supports_websockets = true is configured for a Responses provider, Codex correctly starts by using the WebSocket transport. However, if a WebSocket streaming request disconnects and exhausts the configured stream_max_retries, the session falls back to HTTPS/SSE and appears to keep WebSockets disabled for the rest of that Codex session.
This is helpful for environments where WebSockets are genuinely unsupported, such as some proxies, but it also means a transient network/provider interruption can leave an otherwise healthy session on HTTPS/SSE until Codex is restarted.
Why this matters
For temporary failures, the service or network may recover shortly after fallback. In that case, users would expect Codex to eventually try WebSockets again without needing to restart the app/session.
At the same time, the recovery behavior should not make permanently broken proxy environments worse by repeatedly trying WebSockets forever.
Suggested behavior
Keep the existing supports_websockets configuration and avoid adding a new user-facing setting.
A possible approach:
- after WebSocket fallback activates, disable WebSockets only for a cooldown window instead of permanently for the session
- allow a small bounded number of recovery attempts per session
- after the recovery budget is exhausted, leave the session on HTTPS/SSE
- reset the recovery budget only after a WebSocket stream reaches
response.completed, not just after the WebSocket connection opens
This would let transient failures recover automatically while preserving the current fallback behavior for proxies or environments that consistently fail WebSocket transport.
Prototype branch
I prepared a small prototype branch here:
https://github.com/qindongliang/codex/tree/codex/websocket-fallback-recovery
Commit:
067853cb79 Recover websocket transport after fallback cooldown
The branch uses cooldown-based recovery and resets the recovery budget after a successful WebSocket completion. I could not open a PR because this repository currently limits pull request creation to collaborators.
Local testing
From the prototype branch:
just fmtgit diff --checkRUSTC="$HOME/.rustup/toolchains/1.93.0-aarch64-apple-darwin/bin/rustc" "$HOME/.rustup/toolchains/1.93.0-aarch64-apple-darwin/bin/cargo" test -p codex-core websocket_PATH="$HOME/.rustup/toolchains/1.93.0-aarch64-apple-darwin/bin:$PATH" RUSTC="$HOME/.rustup/toolchains/1.93.0-aarch64-apple-darwin/bin/rustc" just fix -p codex-core
The targeted WebSocket test run passed: 6 unit tests and 48 filtered integration tests.