{"id":"c98fc5775361023e","repo":"redis/redis-py","slug":"bad-response-from-ping-health-check","errorCode":null,"errorMessage":"Bad response from PING health check","messagePattern":"Bad response from PING health check","errorType":"exception","errorClass":"ConnectionError","httpStatus":null,"severity":"error","filePath":"redis/asyncio/connection.py","lineNumber":1149,"sourceCode":"            await record_connection_closed(\n                close_reason=CloseReason.APPLICATION_CLOSE,\n            )\n\n        if self.maintenance_state == MaintenanceState.MAINTENANCE:\n            # MOVING state is owned by the pool-level TTL cleanup. Regular\n            # maintenance timeout relaxation can be restored when this\n            # connection closes, matching the sync lifecycle.\n            self.reset_tmp_settings(reset_relaxed_timeout=True)\n            self.maintenance_state = MaintenanceState.NONE\n            # reset the sets that keep track of received start maint\n            # notifications and skipped end maint notifications\n            self.reset_received_notifications()\n\n    async def _send_ping(self):\n        \"\"\"Send PING, expect PONG in return\"\"\"\n        await self.send_command(\"PING\", check_health=False)\n        if str_if_bytes(await self.read_response()) != \"PONG\":\n            raise ConnectionError(\"Bad response from PING health check\")\n\n    async def _ping_failed(self, error, failure_count):\n        \"\"\"Function to call when PING fails\"\"\"\n        await self.disconnect(\n            error=error, failure_count=failure_count, health_check_failed=True\n        )\n\n    async def check_health(self):\n        \"\"\"Check the health of the connection with a PING/PONG\"\"\"\n        if (\n            self.health_check_interval\n            and asyncio.get_running_loop().time() > self.next_health_check\n        ):\n            await self.retry.call_with_retry(\n                self._send_ping, self._ping_failed, with_failure_count=True\n            )\n\n    async def _send_packed_command(self, command: Iterable[bytes]) -> None:","sourceCodeStart":1131,"sourceCodeEnd":1167,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/asyncio/connection.py#L1131-L1167","documentation":"Raised as a ConnectionError by _send_ping() during check_health(): a PING was sent and a response came back, but it was not 'PONG'. This almost always means the response stream is desynchronized or something else (a proxy, a pub/sub message, a protocol parser bug) injected a non-PONG reply where the PONG was expected.","triggerScenarios":"check_health() runs when health_check_interval is set and the connection is idle past next_health_check; _send_ping() reads via read_response() and the bytes are not b'PONG'. Also reachable if a proxy mangles PING/PONG, or if the connection was reused after a partial/errored prior command left unread bytes.","commonSituations":"A misbehaving proxy rewriting PING; pub/sub or push data (RESP3 out-of-band) arriving between the PING send and read; a parser desync after a previous truncated command; running commands on a connection concurrently from two coroutines (corrupts the request/response pairing).","solutions":["Ensure only one coroutine uses a connection at a time (the pool enforces this; do not share a raw connection across tasks).","Set health_check_interval=0 to disable health checks if the intermediary cannot pass PING/PONG cleanly, or remove the misbehaving proxy.","Check for unread responses left over from prior commands (e.g. a command whose response was never read).","Upgrade redis-py and the server; report a parser bug if it reproduces against a vanilla Redis."],"exampleFix":"// before\nr = redis.asyncio.Redis(host=h, port=p, health_check_interval=30)\n\n# with a proxy that breaks PING\n\n// after\nr = redis.asyncio.Redis(host=h, port=p, health_check_interval=0)","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"from redis.exceptions import ConnectionError\nfor attempt in range(3):\n    try:\n        await r.get(\"k\")\n        break\n    except ConnectionError as e:\n        if \"PING health check\" in str(e):\n            await asyncio.sleep(2 ** attempt)\n            continue\n        raise","preventionTips":["Never share one connection across concurrent coroutines.","Disable health_check_interval if an intermediary corrupts PING/PONG.","Always read every response you request to keep the stream in sync."],"tags":["health-check","connection","protocol","proxy","async"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}