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 opcode

View on GitHub (pinned to c0ef574e29)

Solutions

  1. In your receive loop, check 'if msg.type is WSMsgType.ERROR' and inspect msg.data.code to detect the 1002 and close cleanly.
  2. Verify the peer implementation starts every fragmented message with a TEXT (0x1) or BINARY (0x2) frame before sending CONTINUATION (0x0).
  3. Check intermediaries (nginx/haproxy/envoy) for WebSocket frame corruption and test with a direct connection.
  4. 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

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.