You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
After upgrading from 1.22.0 to 1.23.0-beta.5, Codex clients using supports_websockets = true frequently see websocket closed by server before response.completed, followed by reconnect/retry loops.
Server-side, two things changed:
A warning that never appeared once on 1.22.0 now fires continuously: Websocket scope cleanup exceeded its remaining drain budget
Mid-turn interruption/resend markers rose by roughly 28–35x per request
v1.23.0 (stable) contains no WebSocket changes relative to v1.23.0-beta.5, so this is presumably still present on stable.
This is a fairly high-throughput deployment with a large SQLite store, so it may only show up under load — that might be why it survived the beta soak.
Measured comparison
Same workload, same store, same client fleet across both versions — only the image changed. Normalized per 1,000 POST /v1/responses, each measured over a multi-day window:
marker
1.22.0
1.23.0-beta.5
Websocket scope cleanup exceeded its remaining drain budget
Outside of shutdown, remaining_drain_timeout_seconds() returns None, so the budget falls back to _TASK_CANCEL_TIMEOUT_SECONDS = 1.0 (app/modules/proxy/_service/http_bridge/helpers.py:194) — a hardcoded 1 second with no config or env override.
So on a healthy, non-draining server the finalization path (release_current_account_lease() plus request finalization) gets a fixed 1s and routinely exceeds it. On a busy instance with a large store, 1s seems easy to blow through.
I could not determine from stdout which side closes first: the distinguishing strings (Downstream websocket disconnected before response.completed / Upstream websocket closed before response.completed / Websocket scope cancelled before response.completed) are passed as error_message into request-log finalization and never reach the container log. I can pull them from the request-log DB if that would help narrow it down.
Slow finalization writes are a plausible reason a 1s budget is exceeded, but I have not measured finalization latency, so I can't claim that's the cause rather than a correlate.
Summary
After upgrading from
1.22.0to1.23.0-beta.5, Codex clients usingsupports_websockets = truefrequently seewebsocket closed by server before response.completed, followed by reconnect/retry loops.Server-side, two things changed:
Websocket scope cleanup exceeded its remaining drain budgetv1.23.0(stable) contains no WebSocket changes relative tov1.23.0-beta.5, so this is presumably still present on stable.This is a fairly high-throughput deployment with a large SQLite store, so it may only show up under load — that might be why it survived the beta soak.
Measured comparison
Same workload, same store, same client fleet across both versions — only the image changed. Normalized per 1,000
POST /v1/responses, each measured over a multi-day window:Websocket scope cleanup exceeded its remaining drain budgetwebsocket_interrupted_tool_outputs_injectedwebsocket_client_previous_response_full_resend_retry_preparedThe rate is bursty — it climbs sharply during busy hours, which is consistent with a load-dependent trigger.
Where the warning comes from
app/modules/proxy/_service/websocket/mixin.py:2609with (line 2456):
Outside of shutdown,
remaining_drain_timeout_seconds()returnsNone, so the budget falls back to_TASK_CANCEL_TIMEOUT_SECONDS = 1.0(app/modules/proxy/_service/http_bridge/helpers.py:194) — a hardcoded 1 second with no config or env override.So on a healthy, non-draining server the finalization path (
release_current_account_lease()plus request finalization) gets a fixed 1s and routinely exceeds it. On a busy instance with a large store, 1s seems easy to blow through.Environment
1.23.0-beta.5, Docker (ghcr.io/soju06/codex-lb:1.23.0-beta.5)0.147.0-alpha.6.5,wire_api = "responses",supports_websockets = trueNotes / limitations
Downstream websocket disconnected before response.completed/Upstream websocket closed before response.completed/Websocket scope cancelled before response.completed) are passed aserror_messageinto request-log finalization and never reach the container log. I can pull them from the request-log DB if that would help narrow it down.fix(proxy): extend websocket stream budget(fix(proxy): extend websocket stream budget #1353, merged 2026-07-27) is already included in beta.5, so it does not address this.Workaround
Setting
supports_websockets = falseon the client avoids the WS path entirely.Questions
interrupted_tool_outputs_injected/full_resend_retry_prepareda known consequence of a 1.23.0-beta change?