Skip to content

Instantly share code, notes, and snippets.

@lawrence-forooghian
Created March 6, 2026 19:07
Show Gist options
  • Select an option

  • Save lawrence-forooghian/0b853e2661caa9fe964355a703a4cecd to your computer and use it in GitHub Desktop.

Select an option

Save lawrence-forooghian/0b853e2661caa9fe964355a703a4cecd to your computer and use it in GitHub Desktop.
WebSocket close code handling across Ably client SDKs

WebSocket Close Code Handling Across Ably Client SDKs

Survey of whether Ably client libraries implement the guidance in the client library specification about how to handle different WebSocket close codes.

What the spec says

The spec defines how different RFC 6455 close codes should map to connection states:

Should trigger disconnected/suspended (retryable):

  • GOING_AWAY (1001)
  • ABNORMAL_CLOSE (1006)

Should trigger failed (terminal):

  • CLOSE_PROTOCOL_ERROR (1002) — reason code 80000
  • REFUSE (1003) — reason code 0100
  • NO_UTF8 (1007) — reason code 80000
  • POLICY_VALIDATION (1008) — reason code 0100
  • TOOBIG (1009) — reason code 40000
  • EXTENSION (1010) — reason code 80000
  • UNEXPECTED_CONDITION (1011) — reason code 80000
  • TLS_ERROR (1015) — reason code 80000

Results

Fully implements the spec guidance

SDK Commit Details
ably-java 16f03d1 Comprehensive switch statement in WebSocketTransport.java (lines 310–347). Defines all close code constants (lines 31–43). Maps codes to disconnected, refused, too-big, or failed states.
ably-cocoa 14d2661 Comprehensive switch statement in ARTWebSocketTransport.m (lines 278–325). Defines close code enum (lines 24–37). Same state mapping as Java.

Inherits from native SDKs

SDK Commit Details
ably-flutter 6a93ac5 Delegates to ably-java (Android) and ably-cocoa (iOS). No close code logic in the Flutter layer itself — state changes are passed through from the native SDKs.

Does NOT implement the spec guidance

SDK Commit Details
ably-js 9ec7737 Only checks for code 1000 (clean close). All other codes produce a generic disconnected state with error 80003. No constants, enums, or mappings for other close codes. See websockettransport.ts lines 163–187.
ably-dotnet f8a6f89 Logs the close status via ClientWebSocket.CloseStatus but treats all closures generically. No conditional logic based on specific close codes. See MsWebSocketConnection.cs lines 187–203.
ably-go 17f5d49 All WebSocket errors treated uniformly. Close codes are not extracted or examined. The recoverable() function only checks Ably error codes (40000–49999 range), not WebSocket close codes. See realtime_conn.go.
ably-python 324a9f6 Distinguishes ConnectionClosedOK (code 1000) from generic WebSocketException. No code-specific handling — all non-1000 closures trigger generic retry logic. See websockettransport.py.
ably-ruby b303a8e Close code is captured as a log string ("#{event.code}: #{event.reason}") but never used for conditional logic. All closures transition to disconnected with retry. See websocket_transport.rb lines 240–242.

Not applicable (REST-only SDKs)

SDK Commit Notes
ably-rust d62743f REST-only client; no realtime/WebSocket functionality.
ably-php ebc27a9 REST-only client; no realtime/WebSocket functionality.

Not surveyed

ably-swift was not included in this survey.

Impact

In the 5 non-compliant realtime SDKs, close codes that the spec says should trigger a failed (terminal) state — such as 1002 (CLOSE_PROTOCOL_ERROR), 1007 (NO_UTF8), 1010 (EXTENSION), 1011 (UNEXPECTED_CONDITION), and 1015 (TLS_ERROR) — will instead trigger reconnection attempts. This means the client will keep retrying a connection that the server has indicated is fundamentally broken.

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