Background
handshakeStatus (src/websocket/utils.ts) recovers the HTTP status of a failed
WebSocket/SSE handshake by parsing the error message text from two libraries:
ws — Unexpected server response: <code>
eventsource — Non-200 status code (<code>)
Neither string is a public contract; both are plain template literals
(ws/lib/websocket.js and eventsource/src/EventSource.ts). If either reword on
a version bump, handshakeStatus returns undefined, every unrecoverable status
becomes a retry-forever connection_error, and CoderApi.is404Error stops
falling back to SSE.
A real-library test (test/unit/websocket/utils.test.ts) now guards against a
silent reword by failing in CI, but parsing internal strings is still fragile.
Proposal
Read the status from the libraries' public event APIs instead of the message:
- SSE:
eventsource puts the status on ErrorEvent.code (documented public
API). SseConnection.createErrorEvent (src/websocket/sseConnection.ts)
currently reads only message and error and drops code; surface it.
- WebSocket:
ws exposes the same on its unexpected-response event, which
also suppresses the string path once handled.
Once the status comes from the events, handshakeStatus's string parsing can be
narrowed or removed.
Acceptance
- Handshake status is derived from
ErrorEvent.code / the ws
unexpected-response event, not the error message text.
- Unrecoverable-HTTP handling and the SSE 404 fallback keep working.
Filed by Coder Agents on behalf of @aqandrew, as a follow-up to PR #1100.
Background
handshakeStatus(src/websocket/utils.ts) recovers the HTTP status of a failedWebSocket/SSE handshake by parsing the error message text from two libraries:
ws—Unexpected server response: <code>eventsource—Non-200 status code (<code>)Neither string is a public contract; both are plain template literals
(
ws/lib/websocket.jsandeventsource/src/EventSource.ts). If either reword ona version bump,
handshakeStatusreturnsundefined, every unrecoverable statusbecomes a retry-forever
connection_error, andCoderApi.is404Errorstopsfalling back to SSE.
A real-library test (
test/unit/websocket/utils.test.ts) now guards against asilent reword by failing in CI, but parsing internal strings is still fragile.
Proposal
Read the status from the libraries' public event APIs instead of the message:
eventsourceputs the status onErrorEvent.code(documented publicAPI).
SseConnection.createErrorEvent(src/websocket/sseConnection.ts)currently reads only
messageanderrorand dropscode; surface it.wsexposes the same on itsunexpected-responseevent, whichalso suppresses the string path once handled.
Once the status comes from the events,
handshakeStatus's string parsing can benarrowed or removed.
Acceptance
ErrorEvent.code/ thewsunexpected-responseevent, not the error message text.Filed by Coder Agents on behalf of @aqandrew, as a follow-up to PR #1100.