aio-libs/aiohttp · error · WebSocketError

1002

1002

Error message

Continuation frame for non started message

What it means

Identical semantics to error [4]: WebSocketError (1002 PROTOCOL_ERROR) for a CONTINUATION frame with no preceding non-final TEXT/BINARY frame. The difference is purely the implementation: this one is raised by the PURE-PYTHON reader (reader_py.py) used when Cython extensions are disabled (AIOHTTP_NO_EXTENSIONS=1) or not compiled. Behavior, close code, and surfacing (msg.type == WSMsgType.ERROR) are the same as the Cython variant.

Source

Thrown at aiohttp/_websocket/reader_py.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. Handle msg.type == WSMsgType.ERROR / code 1002 in the receive loop and close.
  2. Ensure the peer starts each fragmented message with a TEXT/BINARY frame before CONTINUATION.
  3. If you did not intend pure-Python mode, build the extensions ('pip install -e .' / make cythonize) to move to the Cython reader; the bug itself is on the peer.
  4. Rule out a frame-corrupting intermediary.

Example fix

# before
PYTHONPATH=. AIOHTTP_NO_EXTENSIONS=1 python app.py  # reader_py.py path
msg = await ws.receive()

# after
msg = await ws.receive()
if msg.type is aiohttp.WSMsgType.ERROR:
    log.warning('protocol error (pure-py reader): %s', msg.data)
    await ws.close()
Defensive patterns

Strategy: type-guard

Type guard

from aiohttp import WSMsgType

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 (pure-py reader): %s', exc)
    await ws.close()

Prevention

When it happens

Trigger: Same as [4]: a CONTINUATION (0x0) frame arrives while self._opcode == OP_CODE_NOT_SET. You only see reader_py.py in the traceback when extensions are off (AIOHTTP_NO_EXTENSIONS=1) or the C extension import failed.

Common situations: Running with AIOHTTP_NO_EXTENSIONS=1 for debugging; a broken/missing Cython build (pip install without compiling extensions); CI environments that force pure-Python mode. The underlying peer bug is the same as [4].

Related errors


AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04). Data as JSON: /data/errors/77659189f302f82e.json. Report an issue: GitHub.