Skip to content

fix(core): retry pre-request WebSocket handshake failures - #4780

Open
CoralGarden52 wants to merge 3 commits into
openai:mainfrom
CoralGarden52:fix/responses-ws-handshake-retry
Open

fix(core): retry pre-request WebSocket handshake failures#4780
CoralGarden52 wants to merge 3 commits into
openai:mainfrom
CoralGarden52:fix/responses-ws-handshake-retry

Conversation

@CoralGarden52

Copy link
Copy Markdown

Summary

OpenAIResponsesWSModel could fail immediately when the peer closed during the WebSocket HTTP upgrade. With the supported websockets>=15 client, a close before a valid HTTP 101 response is reported as websockets.exceptions.InvalidMessage. That exception was raised before _ensure_websocket_connection returned, so the existing pre-event retry path never handled it.

This change:

  • includes connection acquisition in the existing cleanup/retry boundary;
  • recognizes the exact pre-upgrade InvalidMessage case;
  • retries at most once, only before any request frame can have been sent;
  • marks that handshake failure as replay-safe for retry advice;
  • preserves the existing no-replay behavior for send/receive failures after a request may have been transmitted.

Test plan

  • Reproduced with a local raw TCP WebSocket server that closes the first connection before HTTP 101, then completes a valid handshake on the second connection.
  • The production model completed with 2 handshakes, exactly one response.create frame, and response_id=resp_local_probe.
  • make tests: 9292 passed, 29 skipped; serial tests: 77 passed, 4 skipped.
  • .agents/skills/code-change-verification/scripts/run.sh: passed (format, lint, typecheck, and tests).
  • Added regression coverage for handshake retry and replay-safe retry advice.
  • Searched open upstream PRs for InvalidMessage, websocket handshake, pre-event websocket, and websocket retry; no duplicate open PR was found.

Issue number

No existing issue found; the failure was reproduced locally against the real websockets handshake path.

Checks

  • I've added new tests, if relevant
  • I've run .agents/skills/code-change-verification/scripts/run.sh
  • I've confirmed all verification steps pass
  • If using Codex, I've run a read-only code review before submitting this PR

This PR is intentionally opened as a draft pending maintainer review.

@CoralGarden52
CoralGarden52 marked this pull request as ready for review August 30, 2026 11:32

@seratch seratch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution. Moving connection acquisition into the retry boundary addresses the reported pre-request disconnect.

Please narrow the new InvalidMessage handling to failures caused by EOFError, matching the transient-handshake classification in websockets. Other malformed HTTP responses should retain their existing exception behavior without automatically suggesting retries.

Please update the regression coverage to distinguish these cases, verify that repeated EOF failures exhaust the single internal retry without sending a request, and cover close() during a failing handshake so it cannot trigger another connection attempt.

@seratch seratch changed the title fix(models): retry pre-request WebSocket handshake failures fix(core): retry pre-request WebSocket handshake failures Sep 7, 2026
@CoralGarden52

Copy link
Copy Markdown
Author

Thank you for the detailed guidance, @seratch. I’ve submitted a follow-up commit that narrows InvalidMessage retry handling to cases directly caused by EOFError, matching websockets’ transient-handshake classification. Other malformed HTTP responses retain their existing exception behavior without retry advice. I also updated the regression coverage for retry exhaustion without sending a request and for close() during a failing handshake. Focused and full verification passed.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 822d8b81b3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

yielded_terminal_event = False
sent_request_frame = False
try:
connection = await self._await_websocket_with_timeout(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recheck close generation after the retry handshake

When the first EOF handshake failure triggers the new retry and close() runs while the second handshake is pending, close() sees the request lock but no cached connection and returns after incrementing the generation. If this awaited handshake then succeeds, _ensure_websocket_connection() caches the new socket and execution sends the request because the generation is checked only in the exception path. Thus a request and persistent connection can survive an explicit completed close(); revalidate request_close_generation immediately after connection acquisition and dispose the newly opened connection before sending when it changed.

AGENTS.md reference: AGENTS.md:L149-L149

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. _iter_websocket_response_events() now revalidates request_close_generation immediately after _ensure_websocket_connection() returns and before sending the request frame. If close() completed while the retry handshake was pending, the newly acquired socket is disposed and the request aborts without sending or leaving a cached connection.

Added regression coverage for this race, including verification that no request frame is sent and no persistent connection remains.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants