OtterMind/Chat2DB · warning · RequestError

rate limit exceeded

Error message

rate limit exceeded

What it means

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.

Source

Thrown at script/github/qq_relay/relay_server.py:264

            delivery_id, message = self._validate_payload(self._read_payload())
            existing = self.relay_state.deliveries.reserve(delivery_id)
            if existing is not None:
                if existing == "pending":
                    raise RequestError(
                        HTTPStatus.SERVICE_UNAVAILABLE, "delivery is still in progress"
                    )
                self._send_json(
                    HTTPStatus.OK,
                    {
                        "ok": True,
                        "duplicate": True,
                        "message_id": existing,
                    },
                )
                return
            reserved = True
            if not self.relay_state.rate_limiter.acquire():
                raise RequestError(HTTPStatus.TOO_MANY_REQUESTS, "rate limit exceeded")
            url_removed = False
            try:
                message_id = self.relay_state.onebot.send_group_message(message)
            except OneBotRejected:
                fallback_message = _remove_urls(message)
                if fallback_message == message:
                    raise
                message_id = self.relay_state.onebot.send_group_message(fallback_message)
                url_removed = True
            self.relay_state.deliveries.complete(delivery_id, message_id)
            reserved = False
            self._send_json(
                HTTPStatus.OK,
                {
                    "ok": True,
                    "duplicate": False,
                    "message_id": message_id,
                    "url_removed": url_removed,

View on GitHub (pinned to 5ee1e990e7)

Solutions

  1. Back off and retry the same delivery_id after the window; respect the 429.
  2. Raise RELAY_RATE_LIMIT on the relay to match the expected burst size.
  3. Throttle/coalesce events on the sender so they arrive below the cap.

Example fix

# before
resp = requests.post(url, json=payload)
# after
resp = requests.post(url, json=payload)
if resp.status_code == 429:
    time.sleep(60)  # wait out the default 60s window
    resp = requests.post(url, json=payload)  # retry same delivery_id
Defensive patterns

Strategy: retry

Validate before calling

# client-side throttle to stay under the relay's window
import time
rate_limit = int(os.environ.get('RELAY_RATE_LIMIT', '30'))
min_interval = 60 / rate_limit
time.sleep(max(0.0, min_interval - (time.monotonic() - last_send)))

Try / catch

resp = requests.post(url, json=payload)
if resp.status_code == 429:
    retry_after = int(resp.headers.get('Retry-After', '60'))
    time.sleep(retry_after)
    resp = requests.post(url, json=payload)  # same delivery_id

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of OtterMind/Chat2DB@5ee1e990e7 (2026-08-14). Data as JSON: /api/errors/246fb1d0a9168319. Report an issue: GitHub.