aio-libs/aiohttp · error · WebSocketError
1002
1002
Error message
Continuation frame for non started message
What it means
A WebSocket CONTINUATION frame (opcode 0x00) arrived but the reader has no outstanding fragmented message (`_opcode == OP_CODE_NOT_SET`). RFC 6455 §5.4 requires a continuation frame to follow a non-final (fin=0) text/binary frame. The parser treats this as a protocol error and closes the connection with code 1002. It is delivered to user code as a `WSMessageError` in the `receive()` loop, not as a thrown exception.
Solutions
- Fix the peer so a fragmented message starts with TEXT/BINARY (fin=0) and continues with CONTINUATION frames.
- Handle `WSMsgType.ERROR` in your receive loop and reconnect — the malformed peer cannot be recovered mid-stream.
- If you control the peer, validate its frame sequence with a WS conformance suite.
Defensive patterns
Strategy: type-guard
Type guard
def is_error(msg) -> bool:
return msg.type == aiohttp.WSMsgType.ERROR Try / catch
msg = await ws.receive()
if msg.type == aiohttp.WSMsgType.ERROR:
exc = msg.data # a WebSocketError
close_code = exc.code # 1002/1007/1009/...
log.warning("ws protocol error %s: %s", close_code, exc) Prevention
- Always check `msg.type == WSMsgType.ERROR` in receive loops; `msg.data` is the `WebSocketError` with `.code`.
- On a 1002 protocol error, close and reconnect rather than continuing.
When it happens
Trigger: The remote peer sends opcode 0x00 as the very first frame of a message, or immediately after a completed message — i.e. a continuation with no preceding non-fin data frame. Fires in `_handle_frame` at `reader_c.py:208`.
Common situations: Buggy peer fragmentation logic; a proxy/MitM reorders frames; a fuzzer/attacker sending raw frames; a peer library bug that opens a fragmented message with the continuation opcode.
Related errors
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/58af288ca5cb1a36.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/_websocket/reader_c.py:208
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 d041d4d0fd)