aio-libs/aiohttp · error · WebSocketError
WSCloseCode.PROTOCOL_ERROR
WSCloseCode.PROTOCOL_ERROR
Error message
Continuation frame for non started message
What it means
Raised as WebSocketError(code=PROTOCOL_ERROR) in WebSocketReader._handle_frame at reader_py.py:205 when a CONTINUATION opcode (0x0) frame arrives but self._opcode is OP_CODE_NOT_SET, i.e. there is no in-progress fragmented text/binary message to continue. Per RFC 6455 a continuation frame must follow a non-final (FIN=0) text/binary frame.
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 opcodeView on GitHub (pinned to d9aaf697c2)
Solutions
- Inspect `msg.type == aiohttp.WSMsgType.ERROR` and `msg.data.code` in your receive loop and close the connection cleanly with `await ws.close()`.
- If you control the peer, ensure continuation frames are only sent after a FIN=0 text/binary frame.
- Reconnect / re-establish the WebSocket session after a PROTOCOL_ERROR, since the stream is no longer trustworthy.
Example fix
// before
async for msg in ws:
process(msg.data)
# after
async for msg in ws:
if msg.type == aiohttp.WSMsgType.ERROR:
log.warning("ws protocol error: %r", msg.data)
await ws.close()
break
process(msg.data) Defensive patterns
Strategy: try-catch
Try / catch
msg = await ws.receive()
if msg.type == aiohttp.WSMsgType.ERROR:
err = msg.data # WebSocketError with .code == WSCloseCode.PROTOCOL_ERROR
await ws.close()
return Prevention
- Always branch on WSMsgType.ERROR in every receive() / async-for loop; do not assume only data messages arrive.
- Treat PROTOCOL_ERROR as fatal: close and reconnect rather than continuing to read.
- If you control the peer, only emit continuation frames after a FIN=0 data frame.
When it happens
Trigger: The remote peer sends a frame with opcode 0x0 (continuation) as its very first data frame, or after a previous fragmented message was already completed. Surfaces to the aiohttp user via `await ws.receive()` returning a message with type WSMsgType.ERROR (data is the WebSocketError) and triggers a close with code 1002.
Common situations: A buggy or non-conformant client/server implementation; a man-in-the-middle or proxy that reorders/drops frames; a fuzz tester; a peer that restarted its frame state machine mid-stream.
Related errors
- WSCloseCode.PROTOCOL_ERROR
- WSCloseCode.INVALID_TEXT
- Invalid upgrade header
- Invalid connection header
- Invalid challenge response
AI-assisted analysis of aio-libs/aiohttp@d9aaf697c2 (2026-08-06).
Data as JSON: /api/errors/8f08de3b1cd08d46.
Report an issue: GitHub.