aio-libs/aiohttp · error · WebSocketError

WSCloseCode.INVALID_TEXT

WSCloseCode.INVALID_TEXT

Error message

Invalid UTF-8 text message

What it means

Raised as WebSocketError(INVALID_TEXT, code 1007) at reader_py.py:272 when a fully-assembled TEXT frame's payload cannot be decoded as UTF-8 (payload_merged.decode('utf-8') raises UnicodeDecodeError). This enforces the RFC 6455 requirement that text frames contain valid UTF-8. Only reached when self._decode_text is True (the default).

Source

Thrown at aiohttp/_websocket/reader_py.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. Have the peer send non-text data on a binary (opcode 0x2) frame via `ws.send_bytes(data)`.
  2. If you must tolerate invalid UTF-8, construct WebSocketResponse / ClientWebSocketResponse with autoping=True and handle WSMsgType.ERROR by closing and reconnecting.
  3. On the receiver, check `msg.type == WSMsgType.ERROR` and inspect `msg.data.code == WSCloseCode.INVALID_TEXT` to report a clear upstream bug.

Example fix

// before (peer side)
await ws.send_str(binary_payload)  # sent as text, may be non-UTF-8

# after (peer side)
await ws.send_bytes(binary_payload)  # sent as a binary frame
Defensive patterns

Strategy: try-catch

Validate before calling

# on the SENDER side, ensure text frames carry UTF-8
payload = data.encode('utf-8') if isinstance(data, str) else data
if isinstance(data, (bytes, bytearray)):
    await ws.send_bytes(data)   # binary frame - safe for non-UTF-8
else:
    await ws.send_str(data)     # text frame - must be valid UTF-8

Type guard

def is_valid_utf8_text(data: object) -> bool:
    if isinstance(data, str):
        return True
    if isinstance(data, (bytes, bytearray)):
        try:
            data.decode('utf-8'); return True
        except UnicodeDecodeError:
            return False
    return False

Try / catch

msg = await ws.receive()
if msg.type == aiohttp.WSMsgType.ERROR and msg.data.code == aiohttp.WSCloseCode.INVALID_TEXT:
    await ws.close()
    # peer sent non-UTF-8 on a text frame; report upstream bug

Prevention

When it happens

Trigger: The remote peer sends a text (opcode 0x1) frame whose assembled payload is not valid UTF-8, e.g. raw binary mislabelled as text, truncated multi-byte sequences, or mixed encodings. Delivered as WSMsgType.ERROR and closes the connection with code 1007.

Common situations: Peer sending bytes/ binary data on a text frame instead of using a binary frame; a producer that concatenates partial UTF-8 chunks across non-spec fragments; legacy/buggy client; a transcoding proxy corrupting bytes.

Understand the failure class

Related errors


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