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
- Ensure the remote peer only sends valid UTF-8 in TEXT frames; send binary payloads as BINARY frames instead.
- If you must accept raw bytes, open the connection with decode_text=False so TEXT payloads are returned as bytes without validation.
- 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
- If you tolerate non-UTF-8 TEXT frames, open the connection with decode_text=False and decode defensively yourself.
- Send binary payloads as BINARY frames, never mislabelled as TEXT.
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- WSCloseCode.INVALID_TEXT
- Invalid window size
- Extension for deflate not supported{ext.group(1)}
- Compress wbits must between 9 and 15, zlib does not support
- WSCloseCode.PROTOCOL_ERROR
AI-assisted analysis of aio-libs/aiohttp@d9aaf697c2 (2026-08-06).
Data as JSON: /api/errors/05ebeb332de4c6d6.
Report an issue: GitHub.