{"record":{"id":"980a70f94f1eade3","repo":"redis/redis-py","slug":"protocol-error-raw-r","errorCode":null,"errorMessage":"Protocol Error: {raw!r}","messagePattern":"Protocol Error: (.+?)","errorType":"exception","errorClass":"InvalidResponse","httpStatus":null,"severity":"critical","filePath":"redis/_parsers/resp2.py","lineNumber":71,"sourceCode":"            pass\n        # int value\n        elif byte == b\":\":\n            return int(response)\n        # bulk response\n        elif byte == b\"$\" and response == b\"-1\":\n            return None\n        elif byte == b\"$\":\n            response = self._buffer.read(int(response), timeout=timeout)\n        # multi-bulk response\n        elif byte == b\"*\" and response == b\"-1\":\n            return None\n        elif byte == b\"*\":\n            response = [\n                self._read_response(disable_decoding=disable_decoding, timeout=timeout)\n                for i in range(int(response))\n            ]\n        else:\n            raise InvalidResponse(f\"Protocol Error: {raw!r}\")\n\n        if disable_decoding is False:\n            response = self.encoder.decode(response)\n        return response\n\n\nclass _AsyncRESP2Parser(_AsyncRESPBase):\n    \"\"\"Async class for the RESP2 protocol\"\"\"\n\n    async def read_response(self, disable_decoding: bool = False):\n        if not self._connected:\n            raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)\n        if self._chunks:\n            # augment parsing buffer with previously read data\n            self._buffer += b\"\".join(self._chunks)\n            self._chunks.clear()\n        self._pos = 0\n        response = await self._read_response(disable_decoding=disable_decoding)","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/_parsers/resp2.py#L53-L89","documentation":"Raised by the sync RESP2 parser when the first byte of a reply line is none of -, +, :, $, *: the bytes on the wire are not valid RESP2 at all. redis.exceptions.InvalidResponse (a RedisError); the raw bytes are included so you can see what arrived. This almost always means misconfiguration or stream corruption, not a transient fault.","triggerScenarios":"Connecting the client to something that doesn't speak RESP (wrong port/service); TLS/plain mismatch (TLS client to a plain port, or plain client to a TLS-only port - you often see the TLS handshake bytes as the 'raw'); a corrupting proxy/load balancer; a half-read buffer left after a previous interrupted command; a RESP3-only type sent to a protocol=2 parser.","commonSituations":"Pointing redis-py at a MySQL/Postgres/memcached port; stunnel/TLS misconfiguration (redis:// to a rediss:// endpoint); an HTTP proxy returning 'HTTP/1.1 400' as the first line; a sidecar that injects non-RESP bytes.","solutions":["Confirm the host:port is actually Redis: redis-cli -h ... -p ... PING.","Verify TLS matches the endpoint: use rediss:// (or ssl=True) for TLS servers, redis:// for plain.","Remove any non-RESP proxy between client and Redis.","If using stunnel/tunneling, confirm it forwards to the Redis port unchanged.","Re-check decode_responses/encoding and the protocol setting."],"exampleFix":"# before - pointed at the wrong service (Postgres!)\nr = redis.Redis(host='db', port=5432)\nr.get('k')  # -> InvalidResponse: Protocol Error: b'E'\n\n# after\nr = redis.Redis(host='redis', port=6379)\nr.get('k')\n\n# TLS fix: use rediss:// for TLS endpoints\nr = redis.Redis.from_url('rediss://redis.example:6379', ssl_cert_reqs='required')","handlingStrategy":"validation","validationCode":"# Cheap preflight: confirm the endpoint speaks RESP before relying on it\nimport socket\n\ndef is_resp_endpoint(host, port, timeout=2):\n    s = socket.create_connection((host, port), timeout)\n    try:\n        s.sendall(b'*1\\r\\n$4\\r\\nPING\\r\\n')\n        return s.recv(8).startswith(b'+PONG')\n    finally:\n        s.close()","typeGuard":"from redis.exceptions import InvalidResponse\n\ndef is_protocol_error(e: BaseException) -> bool:\n    return isinstance(e, InvalidResponse) and str(e).startswith('Protocol Error')","tryCatchPattern":"from redis.exceptions import InvalidResponse\ntry:\n    r.get('k')\nexcept InvalidResponse as e:\n    raise SystemExit(f'Wire is not RESP - check host/TLS/proxy: {e}') from e","preventionTips":["InvalidResponse is NOT transient - do not retry blindly; it indicates a wrong endpoint, TLS/plaintext mismatch, or a corrupting proxy.","Use rediss:// for TLS endpoints and ssl_cert_reqs='required' in production; plaintext-to-TLS-port produces a Protocol Error.","If you see HTTP/HTML in the raw bytes, you're pointed at an HTTP service or proxy, not Redis."],"tags":["protocol","resp2","config","tls","corruption","invalid-response"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}