{"record":{"id":"f3f22561da65aeea","repo":"Significant-Gravitas/AutoGPT","slug":"rate-limit-service-degraded-retry-shortly","errorCode":null,"errorMessage":"Rate limit service degraded, retry shortly","messagePattern":"Rate limit service degraded, retry shortly","errorType":"http","errorClass":"HTTPException","httpStatus":503,"severity":"error","filePath":"autogpt_platform/backend/backend/api/features/chat/routes.py","lineNumber":1469,"sourceCode":"        try:\n            daily_limit, weekly_limit, _ = await get_global_rate_limits(\n                user_id,\n                config.daily_cost_limit_microdollars,\n                config.weekly_cost_limit_microdollars,\n            )\n            await check_rate_limit(\n                user_id=user_id,\n                daily_cost_limit=daily_limit,\n                weekly_cost_limit=weekly_limit,\n            )\n        except RateLimitExceeded as e:\n            raise HTTPException(status_code=429, detail=str(e)) from e\n        except RateLimitUnavailable as e:\n            # Fail-closed on Redis brown-out: the user may already be at or\n            # past their USD cap and we cannot prove otherwise. 503 + a short\n            # Retry-After is the right UX (transient outage, retry shortly),\n            # not 429 (\"you hit your limit\").\n            raise HTTPException(\n                status_code=503,\n                detail=\"Rate limit service degraded, retry shortly\",\n                headers={\"Retry-After\": \"30\"},\n            ) from e\n\n    # Enrich message with file metadata if file_ids are provided.\n    # Also sanitise file_ids so only validated, workspace-scoped IDs are\n    # forwarded downstream (e.g. to the executor via enqueue_copilot_turn).\n    sanitized_file_ids: list[str] | None = None\n    if request.file_ids:\n        files = await resolve_workspace_files(user_id, request.file_ids)\n        sanitized_file_ids = [wf.id for wf in files] or None\n        request.message += build_files_block(files)\n\n    # Atomically append user message to session BEFORE creating task to avoid\n    # race condition where GET_SESSION sees task as \"running\" but message isn't\n    # saved yet.  append_and_save_message returns None when a duplicate is\n    # detected — both the trailing-same-role check and the","sourceCodeStart":1451,"sourceCodeEnd":1487,"githubUrl":"https://github.com/Significant-Gravitas/AutoGPT/blob/9c8bb5550f446ba5d3046b78896578742495b3cf/autogpt_platform/backend/backend/api/features/chat/routes.py#L1451-L1487","documentation":"HTTP 503 with Retry-After: 30 from POST /chat/stream (routes.py:1469). check_rate_limit raised RateLimitUnavailable because Redis could not report usage. The route deliberately fails CLOSED: as the comment states, the user may already be past their USD cap and the server cannot prove otherwise, so it returns 503 'degraded, retry shortly' rather than 429 'you hit your limit' or silently allowing the message.","triggerScenarios":"POST /chat/stream while Redis (usage counters) is unreachable — RateLimitUnavailable from check_rate_limit after get_global_rate_limits succeeded. Distinct from 429: the user's limits were never evaluated.","commonSituations":"Redis brown-out/restart during peak chat traffic; failover windows; dev environments with flaky Redis. All chat messages fail with 503 until Redis recovers.","solutions":["Retry after the Retry-After window (30s); once Redis is back the request re-evaluates normally.","Ops: check Redis health/connectivity first — this error is an infrastructure signal, not a user-behavior signal.","Client-side: distinguish 503 (retryable, show 'degraded') from 429 (not retryable, show limit UI); do not display 'limit reached' for this error.","Alert on 503 rates from /chat/stream to catch Redis incidents early."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { await post('/chat/stream', body); } catch (e) {\n  if (e.status === 503) { await sleep(Number(e.headers?.['retry-after'] ?? 30) * 1000); return retryOnce(); } // degraded, NOT limit-reached\n  throw e;\n}","preventionTips":["Never render 'usage limit' UI for 503 — it means the limiter itself was unreachable","Backoff-retry 503 with Retry-After; the check re-runs once Redis is back","Alert on 503 rates from /chat/stream as a Redis incident canary"],"tags":["http","redis","availability","fail-closed","rate-limit","chat","fastapi"],"backgroundTag":null,"analyzedSha":"9c8bb5550f446ba5d3046b78896578742495b3cf","analyzedAt":"2026-08-14T17:17:21.957Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}