{"record":{"id":"77659189f302f82e","repo":"aio-libs/aiohttp","slug":"1002-776591","errorCode":"1002","errorMessage":"Continuation frame for non started message","messagePattern":"Continuation frame for non started message","errorType":"exception","errorClass":"WebSocketError","httpStatus":null,"severity":"error","filePath":"aiohttp/_websocket/reader_py.py","lineNumber":208,"sourceCode":"        except Exception as exc:\n            self._exc = exc\n            set_exception(self.queue, exc)\n            return EMPTY_FRAME_ERROR\n\n        return EMPTY_FRAME\n\n    def _handle_frame(\n        self,\n        fin: bool,\n        opcode: int | cython_int,  # Union intended: Cython pxd uses C int\n        payload: bytes | bytearray,\n        compressed: int | cython_int,  # Union intended: Cython pxd uses C int\n    ) -> None:\n        msg: WSMessage\n        if opcode in {OP_CODE_TEXT, OP_CODE_BINARY, OP_CODE_CONTINUATION}:\n            # Validate continuation frames before processing\n            if opcode == OP_CODE_CONTINUATION and self._opcode == OP_CODE_NOT_SET:\n                raise WebSocketError(\n                    WSCloseCode.PROTOCOL_ERROR,\n                    \"Continuation frame for non started message\",\n                )\n\n            # load text/binary\n            if not fin:\n                # got partial frame payload\n                if opcode != OP_CODE_CONTINUATION:\n                    self._opcode = opcode\n                self._partial += payload\n                return\n\n            has_partial = bool(self._partial)\n            if opcode == OP_CODE_CONTINUATION:\n                opcode = self._opcode\n                self._opcode = OP_CODE_NOT_SET\n            # previous frame was non finished\n            # we should get continuation opcode","sourceCodeStart":190,"sourceCodeEnd":226,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/_websocket/reader_py.py#L190-L226","documentation":"Raised as a WebSocketError with close code 1002 (PROTOCOL_ERROR) when an inbound WebSocket frame carrying the CONTINUATION opcode (0x0) arrives while the reader has no in-progress fragmented message (self._opcode is still OP_CODE_NOT_SET). RFC 6455 §5.4 requires that a continuation frame always follow a non-final (fin=0) TEXT or BINARY frame; a continuation with no preceding data-frame start is illegal. The reader only sets self._opcode when a non-fin TEXT/BINARY frame is seen, so this fires for a stray or duplicated continuation.","triggerScenarios":"A peer (or a misbehaving proxy/intermediary) sends a frame with opcode 0x0 (CONTINUATION) as the first data frame, or sends two consecutive fully-assembled messages where the second incorrectly reuses opcode 0x0. Concretely: _handle_frame is entered with opcode == OP_CODE_CONTINUATION while self._opcode == OP_CODE_NOT_SET (-1).","commonSituations":"A buggy client/server implementation that always sends opcode 0 for data frames; a man-in-the-middle proxy that rewrites fragment opcodes; fuzz testing; a custom raw-frame sender built by hand that forgets the first fragment must be TEXT/BINARY.","solutions":["Verify the peer's frame construction: the first frame of every message must use opcode 0x1 (TEXT) or 0x2 (BINARY), and only subsequent fragments use 0x0 (CONTINUATION).","If you control the sender, fix its fragmentation logic so continuation opcodes are only emitted after a non-fin start frame.","If the peer is untrusted/legacy, catch WebSocketError in your receive loop, close the connection with the supplied code (1002), and log the offending frame for diagnosis.","Inspect traffic with a WebSocket-aware debugger (e.g. wireshark) to confirm whether an intermediary is altering opcodes."],"exampleFix":"// before (buggy sender): always sends opcode 0\nws.send_frame(payload, opcode=0x0, fin=False)\n// after: first fragment uses TEXT/BINARY, later fragments use CONTINUATION\nws.send_frame(chunk0, opcode=0x1, fin=False)\nws.send_frame(chunk1, opcode=0x0, fin=True)","handlingStrategy":"try-catch","validationCode":"// On the receiver, wrap the receive loop; you cannot pre-validate a peer's wire bytes.\ntry:\n    msg = await ws.receive()\nexcept WebSocketError as e:\n    await ws.close(code=e.code if hasattr(e,'code') else 1002)\n","typeGuard":"// Identify a stray-continuation protocol error by its code/message.\ndef is_stray_continuation(err: WebSocketError) -> bool:\n    return getattr(err, 'code', None) == WSCloseCode.PROTOCOL_ERROR and 'Continuation frame for non started message' in str(err)\n","tryCatchPattern":"try:\n    async for msg in ws:\n        handle(msg)\nexcept WebSocketError as exc:\n    await ws.close(code=getattr(exc, 'code', WSCloseCode.PROTOCOL_ERROR))\n    log.warning('ws protocol error: %s', exc)\n","preventionTips":["If you control the sender, ensure the first fragment of each message uses TEXT/BINARY and only later fragments use CONTINUATION.","Always pair the receive loop with a WebSocketError handler that closes with the reported code.","Validate frames in a test harness (e.g. autobahn testsuite/fuzzingclient) before shipping a custom WebSocket sender."],"tags":["websocket","protocol-error","rfc6455","continuation","server","client"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}