{"record":{"id":"246fb1d0a9168319","repo":"OtterMind/Chat2DB","slug":"rate-limit-exceeded","errorCode":null,"errorMessage":"rate limit exceeded","messagePattern":"rate limit exceeded","errorType":"http","errorClass":"RequestError","httpStatus":429,"severity":"warning","filePath":"script/github/qq_relay/relay_server.py","lineNumber":264,"sourceCode":"            delivery_id, message = self._validate_payload(self._read_payload())\n            existing = self.relay_state.deliveries.reserve(delivery_id)\n            if existing is not None:\n                if existing == \"pending\":\n                    raise RequestError(\n                        HTTPStatus.SERVICE_UNAVAILABLE, \"delivery is still in progress\"\n                    )\n                self._send_json(\n                    HTTPStatus.OK,\n                    {\n                        \"ok\": True,\n                        \"duplicate\": True,\n                        \"message_id\": existing,\n                    },\n                )\n                return\n            reserved = True\n            if not self.relay_state.rate_limiter.acquire():\n                raise RequestError(HTTPStatus.TOO_MANY_REQUESTS, \"rate limit exceeded\")\n            url_removed = False\n            try:\n                message_id = self.relay_state.onebot.send_group_message(message)\n            except OneBotRejected:\n                fallback_message = _remove_urls(message)\n                if fallback_message == message:\n                    raise\n                message_id = self.relay_state.onebot.send_group_message(fallback_message)\n                url_removed = True\n            self.relay_state.deliveries.complete(delivery_id, message_id)\n            reserved = False\n            self._send_json(\n                HTTPStatus.OK,\n                {\n                    \"ok\": True,\n                    \"duplicate\": False,\n                    \"message_id\": message_id,\n                    \"url_removed\": url_removed,","sourceCodeStart":246,"sourceCodeEnd":282,"githubUrl":"https://github.com/OtterMind/Chat2DB/blob/5ee1e990e73fbcae1969dc554be254fedb3ab888/script/github/qq_relay/relay_server.py#L246-L282","documentation":"Raised at relay_server.py:264 when RateLimiter.acquire() returns False: more than config.rate_limit (default 30) accepted deliveries have started within the last config.rate_window_seconds (default 60). The limiter is a sliding window (relay_server.py:83) shared across all clients. Returned as HTTP 429. Note the delivery slot is released on this path (relay_server.py:286), so the delivery_id is free to retry.","triggerScenarios":"A burst of GitHub events within the window exceeds the cap; many repos or many events forwarded through one relay; a flood from a large PR or CI run.","commonSituations":"Default 30/min is too low for an active repo and RELAY_RATE_LIMIT was not raised; sender has no client-side throttling and floods on reconnect.","solutions":["Back off and retry the same delivery_id after the window; respect the 429.","Raise RELAY_RATE_LIMIT on the relay to match the expected burst size.","Throttle/coalesce events on the sender so they arrive below the cap."],"exampleFix":"# before\nresp = requests.post(url, json=payload)\n# after\nresp = requests.post(url, json=payload)\nif resp.status_code == 429:\n    time.sleep(60)  # wait out the default 60s window\n    resp = requests.post(url, json=payload)  # retry same delivery_id","handlingStrategy":"retry","validationCode":"# client-side throttle to stay under the relay's window\nimport time\nrate_limit = int(os.environ.get('RELAY_RATE_LIMIT', '30'))\nmin_interval = 60 / rate_limit\ntime.sleep(max(0.0, min_interval - (time.monotonic() - last_send)))","typeGuard":null,"tryCatchPattern":"resp = requests.post(url, json=payload)\nif resp.status_code == 429:\n    retry_after = int(resp.headers.get('Retry-After', '60'))\n    time.sleep(retry_after)\n    resp = requests.post(url, json=payload)  # same delivery_id","preventionTips":["Throttle the sender to stay below RELAY_RATE_LIMIT per minute.","Retry with the same delivery_id; the slot is released on the 429 path."],"tags":["http","rate-limit","retry","transient","qq-relay"],"backgroundTag":null,"analyzedSha":"5ee1e990e73fbcae1969dc554be254fedb3ab888","analyzedAt":"2026-08-14T07:05:03.077Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}