aio-libs/aiohttp · error · WebSocketError
1002
1002
Error message
Continuation frame for non started message
What it means
WebSocketError with close code 1002 (PROTOCOL_ERROR) raised in WebSocketReader._handle_frame when a CONTINUATION opcode (0x0) frame arrives but no data message was previously started (self._opcode == OP_CODE_NOT_SET). RFC 6455 requires a continuation frame to follow a non-final TEXT/BINARY frame; a continuation with no preceding start frame is malformed. This variant is from the Cython reader (reader_c.py) used when extensions are compiled.
Source
Thrown at aiohttp/_websocket/reader_c.py:205
except Exception as exc:
self._exc = exc
set_exception(self.queue, exc)
return EMPTY_FRAME_ERROR
return EMPTY_FRAME
def _handle_frame(
self,
fin: bool,
opcode: int | cython_int, # Union intended: Cython pxd uses C int
payload: bytes | bytearray,
compressed: int | cython_int, # Union intended: Cython pxd uses C int
) -> None:
msg: WSMessage
if opcode in {OP_CODE_TEXT, OP_CODE_BINARY, OP_CODE_CONTINUATION}:
# Validate continuation frames before processing
if opcode == OP_CODE_CONTINUATION and self._opcode == OP_CODE_NOT_SET:
raise WebSocketError(
WSCloseCode.PROTOCOL_ERROR,
"Continuation frame for non started message",
)
# load text/binary
if not fin:
# got partial frame payload
if opcode != OP_CODE_CONTINUATION:
self._opcode = opcode
self._partial += payload
return
has_partial = bool(self._partial)
if opcode == OP_CODE_CONTINUATION:
opcode = self._opcode
self._opcode = OP_CODE_NOT_SET
# previous frame was non finished
# we should get continuation opcodeView on GitHub (pinned to c0ef574e29)
Solutions
- In your receive loop, check 'if msg.type is WSMsgType.ERROR' and inspect msg.data.code to detect the 1002 and close cleanly.
- Verify the peer implementation starts every fragmented message with a TEXT (0x1) or BINARY (0x2) frame before sending CONTINUATION (0x0).
- Check intermediaries (nginx/haproxy/envoy) for WebSocket frame corruption and test with a direct connection.
- Capture the raw frames (e.g. tcpdump/Wireshark) to confirm the missing leading data frame.
Example fix
# before
msg = await ws.receive()
print(msg.data) # ignores protocol error, connection silently broken
# after
msg = await ws.receive()
if msg.type is aiohttp.WSMsgType.ERROR:
print('ws protocol error:', msg.data, msg.data.code) # WebSocketError, 1002
await ws.close() Defensive patterns
Strategy: type-guard
Type guard
from aiohttp import WSMsgType, WSMessageError
def is_ws_error(msg) -> bool:
return msg.type is WSMsgType.ERROR Try / catch
msg = await ws.receive()
if msg.type is aiohttp.WSMsgType.ERROR:
exc = msg.data # WebSocketError with .code == 1002
log.warning('continuation-without-start: %s (code %s)', exc, exc.code)
await ws.close() Prevention
- Always check msg.type is WSMsgType.ERROR in your receive loop; WebSocket protocol errors arrive as messages, not exceptions.
- Inspect msg.data.code to distinguish the failure mode (here 1002).
- Ensure the peer starts fragmented messages with TEXT/BINARY before any CONTINUATION.
When it happens
Trigger: The remote peer sends a frame whose opcode is 0x0 (continuation) as the very first data frame, or after a previous message was already finalized. In the normal receive() loop this is surfaced as a message with msg.type == WSMsgType.ERROR and msg.data.code == 1002, and the connection is closed with code 1002.
Common situations: A buggy/non-RFC-6455 client or server that emits continuation frames out of order; a corrupting intermediary (misconfigured proxy/LoadBalancer) that drops or reorders the initial TEXT/BINARY frame; custom low-level frame injection that forgets the leading data frame.
Related errors
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/58af288ca5cb1a36.json.
Report an issue: GitHub.