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

  1. Fix the peer so a fragmented message starts with TEXT/BINARY (fin=0) and continues with CONTINUATION frames.
  2. Handle `WSMsgType.ERROR` in your receive loop and reconnect — the malformed peer cannot be recovered mid-stream.
  3. 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

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 opcode

View on GitHub (pinned to d041d4d0fd)