{"record":{"id":"03998d9e4dd1fccc","repo":"redis/redis-py","slug":"a-non-health-check-response-was-cleaned-by-execute","errorCode":null,"errorMessage":"A non health check response was cleaned by execute_command: {response}","messagePattern":"A non health check response was cleaned by execute_command: (.+?)","errorType":"exception","errorClass":"PubSubError","httpStatus":null,"severity":"error","filePath":"redis/client.py","lineNumber":1247,"sourceCode":"        kwargs = {\"check_health\": not self.subscribed}\n        if not self.subscribed:\n            self.clean_health_check_responses()\n        with self._lock:\n            self._execute(connection, connection.send_command, *args, **kwargs)\n\n    def clean_health_check_responses(self) -> None:\n        \"\"\"\n        If any health check responses are present, clean them\n        \"\"\"\n        ttl = 10\n        conn = self.connection\n        while conn and self.health_check_response_counter > 0 and ttl > 0:\n            if self._execute(conn, conn.can_read, timeout=conn.socket_timeout):\n                response = self._execute(conn, conn.read_response)\n                if self.is_health_check_response(response):\n                    self.health_check_response_counter -= 1\n                else:\n                    raise PubSubError(\n                        \"A non health check response was cleaned by \"\n                        \"execute_command: {}\".format(response)\n                    )\n            ttl -= 1\n\n    def _reconnect(\n        self,\n        conn,\n        error: Optional[Exception] = None,\n        failure_count: Optional[int] = None,\n        start_time: Optional[float] = None,\n        command_name: Optional[str] = None,\n    ) -> None:\n        \"\"\"\n        The supported exceptions are already checked in the\n        retry object so we don't need to do it here.\n\n        In this error handler we are trying to reconnect to the server.","sourceCodeStart":1229,"sourceCodeEnd":1265,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/client.py#L1229-L1265","documentation":"Raised by PubSub.clean_health_check_responses() when, while draining queued socket responses that were expected to be health-check PING replies, the library reads a response that is NOT a recognized health-check response. It indicates the pubsub connection's response stream is out of sync with the library's bookkeeping (health_check_response_counter), so a real pubsub message or unexpected protocol frame was consumed as if it were a health check.","triggerScenarios":"Calling PubSub.execute_command (or any command sent over a subscribed pubsub connection) while health_check_response_counter > 0, or interleaving direct commands with pending health-check PINGs on a subscribed connection. Concretely: a subscribed PubSub where conn.health_check_interval is set and a real message arrives between PING send and the clean_health_check_responses() drain loop in _execute.","commonSituations":"A long-lived pubsub listener with a low health_check_interval under heavy message load; sending raw commands on a PubSub object that is also receiving messages; RESP2/RESP3 protocol mismatches that change the shape of the PING reply; bugs in custom retry logic that re-issue PINGs.","solutions":["Avoid issuing direct commands on a PubSub object that is actively subscribed; use a separate client for command traffic.","If you must send commands, ensure no health-check PINGs are in flight (raise health_check_interval or call check_health at controlled points).","Upgrade redis-py to the latest patch release — response-stream desync bugs in this drain loop have been fixed over time.","If reproducing, capture the exact `response` value in the error message to identify which non-health-check frame is being read.","As a last resort, lower socket_timeout or disable health_check_interval (set to 0) on the connection used for pubsub."],"exampleFix":"// before\npubsub = r.pubsub()\npubsub.subscribe('ch')\npubsub.execute_command('GET', 'key')  # interleaves with health checks\n\n// after\npubsub = r.pubsub()\npubsub.subscribe('ch')\n# use a separate client for command traffic\nvalue = r.get('key')","handlingStrategy":"try-catch","validationCode":"# Before issuing any command on a pubsub connection, confirm no health-check\n# responses are pending and the connection is subscribed cleanly.\nif pubsub.health_check_response_counter > 0:\n    pubsub.clean_health_check_responses()  # may raise PubSubError\n# Prefer not to share the pubsub connection for command traffic at all.","typeGuard":"def is_safe_to_send_on_pubsub(pubsub) -> bool:\n    return (\n        pubsub.connection is not None\n        and pubsub.health_check_response_counter == 0\n    )","tryCatchPattern":"from redis.exceptions import PubSubError\ntry:\n    pubsub.clean_health_check_responses()\nexcept PubSubError as e:\n    # response stream is desynced; reconnect and resubscribe\n    pubsub.close()\n    pubsub = client.pubsub()\n    pubsub.subscribe('channel')","preventionTips":["Use a dedicated client for commands; reserve the PubSub connection for subscriptions.","Keep health_check_interval modest so PINGs do not pile up under load.","Treat any PubSubError from the drain loop as a signal to reconnect and resubscribe."],"tags":["pubsub","health-check","protocol","connection"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}