aio-libs/aiohttp · error · WebSocketError

WSCloseCode.INVALID_TEXT

WSCloseCode.INVALID_TEXT

Error message

Invalid UTF-8 text message

What it means

Raised when a finalized TEXT frame's payload cannot be decoded as UTF-8. Per RFC 6455 §6.1, the payload of a TEXT message MUST be valid UTF-8; aiohttp enforces this when decode_text=True (the default) by calling bytes.decode('utf-8') and wrapping UnicodeDecodeError as WebSocketError(INVALID_TEXT) at reader_c.py:271-274.

Source

Thrown at aiohttp/_websocket/reader_c.py:272

                    ),
                )
                if self._max_msg_size and len(payload_merged) > self._max_msg_size:
                    raise WebSocketError(
                        WSCloseCode.MESSAGE_TOO_BIG,
                        f"Decompressed message exceeds size limit {self._max_msg_size}",
                    )
            elif type(assembled_payload) is bytes:
                payload_merged = assembled_payload
            else:
                payload_merged = bytes(assembled_payload)

            size = len(payload_merged)
            if opcode == OP_CODE_TEXT:
                if self._decode_text:
                    try:
                        text = payload_merged.decode("utf-8")
                    except UnicodeDecodeError as exc:
                        raise WebSocketError(
                            WSCloseCode.INVALID_TEXT, "Invalid UTF-8 text message"
                        ) from exc

                    # XXX: The Text and Binary messages here can be a performance
                    # bottleneck, so we use tuple.__new__ to improve performance.
                    # This is not type safe, but many tests should fail in
                    # test_client_ws_functional.py if this is wrong.
                    msg = TUPLE_NEW(WSMessageText, (text, size, "", WS_MSG_TYPE_TEXT))
                else:
                    # Return raw bytes for TEXT messages when decode_text=False
                    msg = TUPLE_NEW(
                        WSMessageTextBytes, (payload_merged, size, "", WS_MSG_TYPE_TEXT)
                    )
            else:
                msg = TUPLE_NEW(
                    WSMessageBinary, (payload_merged, size, "", WS_MSG_TYPE_BINARY)
                )

View on GitHub (pinned to d9aaf697c2)

Solutions

  1. Ensure the remote peer only sends valid UTF-8 in TEXT frames; send binary payloads as BINARY frames instead.
  2. If you must accept raw bytes, open the connection with decode_text=False so TEXT payloads are returned as bytes without validation.
  3. Catch WebSocketError(INVALID_TEXT) in the receive loop and close the connection per spec.

Example fix

// before
ws = await session.ws_connect(url)  # decode_text=True
async for msg in ws:
    print(msg.data)  # str
// after (accept raw bytes, decode defensively yourself)
ws = await session.ws_connect(url, decode_text=False)
async for msg in ws:
    try:
        text = msg.data.decode('utf-8')
    except UnicodeDecodeError:
        handle_bad_text(msg.data)
Defensive patterns

Strategy: try-catch

Validate before calling

ws = await session.ws_connect(url, decode_text=False)  # receive TEXT payloads as raw bytes

Try / catch

from aiohttp import WSCloseCode, WebSocketError
try:
    async for msg in ws:
        ...
except WebSocketError as exc:
    if exc.code == WSCloseCode.INVALID_TEXT:
        await ws.close(code=WSCloseCode.INVALID_TEXT)

Prevention

When it happens

Trigger: A remote peer sends a TEXT frame (opcode 0x1, possibly assembled from fragments) whose bytes are not valid UTF-8 (lone surrogates, truncated multibyte sequences). The decode at reader_c.py:270 fails and the error is raised.

Common situations: A client that sends raw binary data mislabelled as a TEXT frame, a non-compliant library, or a fragmentation edge case where the server splits in the middle of a multibyte character (valid only if the whole message decodes; this fires only on the assembled message).

Understand the failure class

Related errors


AI-assisted analysis of aio-libs/aiohttp@d9aaf697c2 (2026-08-06). Data as JSON: /api/errors/05ebeb332de4c6d6. Report an issue: GitHub.