Bump global rate limit to 360 and add JSON body on 429#5
Conversation
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 0/1 reviews remaining, refill in 14 minutes and 56 seconds.Comment |
Summary
Two related fixes uncovered by burst-testing the rate-limit boundaries on production.
1. Global bucket: 120 → 360 / min / IP
The global rate-limit bucket is evaluated alongside per-route buckets (search, badges, events, etc.). Whichever is tighter wins. With global at 120 and search at 240, the search bump from PR #4 had zero effect for any same-IP burst — global tripped first.
Verified by bursting
/v1/releasesfrom a single IP: hit 429 at request 121, exactly at the global ceiling, never reached the search bucket's 240.Real client burst pattern that motivates the bump:
/announcements+/categories+/topics+ first/search≈ 4-7 calls in seconds./repo+/releases+/readme+/user= 4 calls./users/{u}/repos+/users/{u}/starred= 2 calls.A user opening a few details pages plus the profile screen burns ~30 slots in under a minute. 120 was too tight. 360 leaves the per-route ceilings (search=240, badges=60, events=3) as the binding limit on real traffic.
VPS perf: 120 req/sec on 4 vCPU is fine. Headroom remains.
2. 429 response: empty body → JSON body
Ktor's RateLimit plugin returns 429 +
Retry-Afterbut with an empty response body. Clients can't surface a useful error message without parsing headers — and many client-side error pipelines expect a JSON body.New
StatusPageshandler for429returns:{ "error": "rate_limited", "message": "Retry after 24s" }The
Retry-Afterheader set by the RateLimit plugin is preserved. Clients that already readRetry-Afterkeep working; clients that read the body now get a parseable error.Out of scope (deferred)
x-ratelimit-*onto 429 itself — would require querying internal RateLimit plugin state, no public API. Workaround: client uses Retry-After + the X-RateLimit-* from the most recent 2xx response.Test plan
./gradlew testgreen./v1/releasesfrom a single IP viaapi-direct.github-store.org; expect 429 at request ~241 (search-bucket ceiling) instead of ~121 (old global).{"error":"rate_limited",...}andRetry-Afteris present.