Skip to content

feat: add shared AI rate limiting for batch analysis - #265

Merged
AmintaCCCP merged 4 commits into
mainfrom
feat/ai-rate-limit-handling
Aug 9, 2026
Merged

feat: add shared AI rate limiting for batch analysis#265
AmintaCCCP merged 4 commits into
mainfrom
feat/ai-rate-limit-handling

Conversation

@AmintaCCCP

@AmintaCCCP AmintaCCCP commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a shared rate limiter for the batch AI analysis pipeline so large runs (e.g. thousands of starred repos) respect provider 429 / per-minute limits instead of hammering the API.

Server

  • Relay upstream rate-limit headers (retry-after, retry-after-ms, x-ratelimit-*, anthropic-ratelimit-*, ratelimit-*, x-should-retry) back to the renderer on /proxy/ai, /proxy/github*, /proxy/webdav
  • Expose Retry-After / retry-after-ms via CORS so the renderer fetch() can read them (back-end proxy mode)

Renderer / AI service

  • New AIRateLimiter (src/services/aiRequestLimiter.ts): global cooldown + jittered exponential backoff, circuit breaker on consecutive 429s, optional per-minute sliding window and concurrency semaphore (both off by default)
  • aiService now throws AIRequestError(status, retryAfterMs) on non-OK responses (OpenAI Responses, Claude, Gemini) and exposes isRateLimitedError / getRetryAfterMsFromError helpers
  • backendAdapter attaches retryAfterMs to translated 429 proxy errors
  • AIAnalysisOptimizer acquires/releases a limiter slot per request, notifies the limiter on 429 with cooldown-aware retry delay; response-time sampling now excludes limiter queuing so adaptive concurrency isn't distorted
  • New optional AIConfig.requestsPerMinute wired into the optimizer at both call sites (RepositoryList, DiscoveryView); default unlimited keeps existing configs unchanged

Testing

  • AIRateLimiter unit tests (RPM window, cooldown/circuit breaker, Retry-After caps, abort)
  • Optimizer 429 integration tests (retry-after retry success, circuit-open failure, non-429 unaffected)
  • Server relay test asserting 429 forwarding and filtering of non-rate-limit headers
  • All checks green: frontend + server tsc, frontend 219 tests, server 68 tests, ESLint clean on touched files

Notes

  • Two pre-existing ESLint errors (server/src/mcp/provider.ts:321, src/services/electronProxy.ts:5) are unrelated and remain

Summary by CodeRabbit

  • New Features

    • Added shared AI request rate limiting with concurrency and requests-per-minute controls.
    • Added automatic cooldowns, retries, backoff, and server-provided retry delays.
    • Added an optional requests-per-minute setting for batch AI analysis.
    • Exposed recognized upstream rate-limit information from supported proxy responses.
  • Bug Fixes

    • Improved handling of AI and backend rate-limit errors and retry timing.
    • Prevented unrelated upstream headers from being exposed through proxy responses.
  • Tests

    • Added coverage for rate limiting, retries, cooldowns, aborts, error parsing, and forwarded headers.

Handle 429/Retry-After/RPM across the batch AI analysis pipeline:

- Add AIRateLimiter (src/services/aiRequestLimiter.ts): shared cooldown +
  jittered exponential backoff, optional per-minute sliding window and
  concurrency semaphore, honors provider Retry-After (capped).
- aiService: throw AIRequestError(status, retryAfterMs) on non-OK responses
  (OpenAI/Claude/Gemini), plus isRateLimitedError/getRetryAfterMsFromError
  helpers used by the limiter.
- Server: relay upstream rate-limit headers (retry-after, retry-after-ms,
  x-ratelimit-*, anthropic-ratelimit-*) on /proxy/ai, /proxy/github*,
  /proxy/webdav; expose Retry-After via CORS so the renderer can read it.
- backendAdapter: attach retryAfterMs to translated 429 proxy errors.
- AIAnalysisOptimizer: acquire/release a limiter slot per request, notify
  on 429 with cooldown-aware retry delay; keep response-time sampling free of
  limiter queuing so adaptive concurrency is not distorted.
- Wire optional AIConfig.requestsPerMinute into optimizer at both call sites
  (default unlimited).

Tests: limiter unit tests, optimizer 429 integration tests, server relay
test; all frontend/server suites green.
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 11b0bc77-3e8c-46eb-8236-ed7c0066a41e

📥 Commits

Reviewing files that changed from the base of the PR and between 9dc1f9f and 63ab38c.

📒 Files selected for processing (1)
  • src/services/aiAnalysisOptimizer.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/services/aiAnalysisOptimizer.test.ts

📝 Walkthrough

Walkthrough

The PR forwards approved upstream rate-limit headers through proxy responses. It adds structured retry metadata, a shared AIRateLimiter, AI optimizer integration, request-limit configuration, and tests for throttling, retries, and abort behavior.

Changes

Proxy rate-limit header forwarding

Layer / File(s) Summary
Allowlisted proxy header relay
server/src/routes/proxy.ts, server/src/index.ts, server/tests/routes/githubProxyRoute.test.ts
Proxy routes forward approved rate-limit headers from upstream responses. CORS exposes the retry and rate-limit headers. Tests exclude cookies and debug headers.

Shared AI request rate limiting

Layer / File(s) Summary
Structured AI retry metadata
src/services/aiService.ts, src/services/backendAdapter.ts, src/types/index.ts, src/services/aiService.test.ts, src/services/backendAdapter.test.ts
AI and backend errors preserve rate-limit status and parsed retry delays. AIConfig accepts an optional requestsPerMinute limit.
AIRateLimiter admission and cooldown
src/services/aiRequestLimiter.ts, src/services/aiRequestLimiter.test.ts
The limiter applies concurrency, sliding-window RPM, cooldown, exponential backoff, bounded Retry-After handling, status reporting, and abort-aware waits.
AI analysis limiter integration
src/services/aiAnalysisOptimizer.ts, src/components/DiscoveryView.tsx, src/components/RepositoryList.tsx, src/services/aiAnalysisOptimizer.test.ts
AI analysis uses shared limiter settings. Requests acquire and release limiter slots, report success or rate limits, combine cooldown time with retry delays, and stop on batch abort.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AIAnalysisOptimizer
  participant AIRateLimiter
  participant AIService
  AIAnalysisOptimizer->>AIRateLimiter: acquire()
  AIRateLimiter-->>AIAnalysisOptimizer: release callback
  AIAnalysisOptimizer->>AIService: execute AI request
  AIService-->>AIAnalysisOptimizer: success or AIRequestError
  AIAnalysisOptimizer->>AIRateLimiter: notifySuccess() or notifyRateLimit(retryAfterMs)
  AIAnalysisOptimizer->>AIRateLimiter: acquire() before retry
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: shared AI rate limiting for batch analysis.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ai-rate-limit-handling

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/src/index.ts`:
- Line 30: Update the CORS configuration’s exposedHeaders list in the server
setup to include X-RateLimit-Remaining, X-RateLimit-Reset, and
x-ratelimit-remaining alongside the existing headers, preserving the proxy
response headers for browser clients.

In `@src/services/aiAnalysisOptimizer.ts`:
- Around line 253-259: Update src/services/aiAnalysisOptimizer.ts lines 253-259
in the worker analysis flow to use one optimizer-level batch abort signal for
both AIRateLimiter.acquire and aiService.analyzeRepository, rather than the
per-worker controller signal. Update lines 291-295 in the retry-delay logic to
make the wait abortable with that same batch signal, ensuring abort immediately
stops limiter waits, AI requests, and retry delays.

In `@src/services/aiRequestLimiter.ts`:
- Around line 70-78: Make the admission logic in acquire atomic: serialize
concurrent acquisitions or combine the concurrency and RPM checks into one loop
that revalidates both limits immediately before incrementing active and
recording requestTimestamps. Preserve abort handling and ensure every successful
reservation updates both counters within the same critical section. Add
concurrent Promise.all tests covering maxConcurrency and requestsPerMinute
limits.
- Around line 108-111: The retry wait calculation in the limiter must never
shorten an accepted provider Retry-After value: apply jitter only to local
exponential backoff, then preserve Retry-After as the minimum before applying
the existing cap and cooldown update. Update src/services/aiRequestLimiter.ts
lines 108-111 accordingly; update src/services/aiRequestLimiter.test.ts lines
65-72 to assert the wait is at least the accepted Retry-After duration.

In `@src/services/backendAdapter.ts`:
- Around line 232-248: Update the Retry-After parsing in the 429 handling of the
backend adapter: parse numeric seconds with Number, and when parsing fails,
compute the delay from Date.parse(retryAfter) minus Date.now(), converting it to
milliseconds and retaining only finite positive values. Preserve retry-after-ms
precedence, and add coverage for both numeric and HTTP-date formats.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a402b6e8-e769-4b13-b026-9f0baa684ff1

📥 Commits

Reviewing files that changed from the base of the PR and between a9b794b and 29b5010.

📒 Files selected for processing (13)
  • server/src/index.ts
  • server/src/routes/proxy.ts
  • server/tests/routes/githubProxyRoute.test.ts
  • src/components/DiscoveryView.tsx
  • src/components/RepositoryList.tsx
  • src/services/aiAnalysisOptimizer.test.ts
  • src/services/aiAnalysisOptimizer.ts
  • src/services/aiRequestLimiter.test.ts
  • src/services/aiRequestLimiter.ts
  • src/services/aiService.test.ts
  • src/services/aiService.ts
  • src/services/backendAdapter.ts
  • src/types/index.ts

Comment thread server/src/index.ts Outdated
Comment thread src/services/aiAnalysisOptimizer.ts Outdated
Comment thread src/services/aiRequestLimiter.ts
Comment thread src/services/aiRequestLimiter.ts Outdated
Comment thread src/services/backendAdapter.ts
- CORS: expose X-RateLimit-Remaining/X-RateLimit-Reset/x-ratelimit-remaining
  so browser clients can read the GitHub rate-limit headers the proxy relays.
- Optimizer: use one batch-level AbortController shared by every worker so
  abort() immediately stops all limiter waits, in-flight AI requests and retry
  delays (previously only the most recent per-attempt controller was aborted);
  retry delay is now abortable via the same batch signal.
- Limiter: make admission atomic by re-validating cooldown/RPM/concurrency
  synchronously right before reserving, so concurrent acquires can never exceed
  maxConcurrency or requestsPerMinute; add Promise.all concurrency tests.
- Limiter: apply jitter only to local exponential backoff and keep the provider
  Retry-After as a floor, so an accepted Retry-After is never shortened.
- BackendAdapter: parse HTTP-date Retry-After (fall back to Date.parse) in
  addition to numeric seconds; add tests for retry-after-ms, numeric and
  HTTP-date forms.
- Docstrings added per CodeRabbit coverage warning.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/services/aiAnalysisOptimizer.ts`:
- Around line 82-86: Update the waitWhilePaused() and both concurrency-monitor
wait paths to use an abort-aware delay tied to batchAbortController.signal, so
abort() interrupts every pending wait. Propagate batchAbortController.signal
through the fetchReadme() backend and GitHub request APIs and their callers,
ensuring all pipelined requests observe cancellation and Promise.all() can
settle after abort.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 64baa851-0716-4c13-88f9-e84d16e3dc80

📥 Commits

Reviewing files that changed from the base of the PR and between 29b5010 and de64a16.

📒 Files selected for processing (7)
  • server/src/index.ts
  • src/services/aiAnalysisOptimizer.test.ts
  • src/services/aiAnalysisOptimizer.ts
  • src/services/aiRequestLimiter.test.ts
  • src/services/aiRequestLimiter.ts
  • src/services/backendAdapter.test.ts
  • src/services/backendAdapter.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/services/backendAdapter.ts
  • src/services/aiAnalysisOptimizer.test.ts
  • server/src/index.ts
  • src/services/aiRequestLimiter.ts

Comment thread src/services/aiAnalysisOptimizer.ts
Follow-up to CodeRabbit review: abort() now also interrupts:
- waitWhilePaused() polling (abortableDelay instead of plain delay)
- both concurrency monitors (abortableDelay(1000))
- the inter-batch delay in prefetchReadmes
- in-flight README fetches in the pipelined flow by propagating the
  batchAbortController.signal into fetchReadme() -> backend.getRepositoryReadme
  / githubApi.getRepositoryReadme, so Promise.all() settles promptly after
  cancellation instead of staying pending on long-running README requests.

Adds a regression test proving abort() settles analyzeRepositoriesPipelined
even while a never-completing README request is still in flight.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/services/aiAnalysisOptimizer.test.ts`:
- Around line 149-155: Update the getRepositoryReadme mock and related
cancellation test to expose a request-start signal, await that signal instead of
relying on the fixed 100 ms delay, and handle an already-aborted AbortSignal
immediately. Track whether the signal’s abort callback fired, then assert that
the README request was actually aborted after the batch cancellation path
completes.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a6ffef9b-fe62-4938-9bf7-652a91ea40d5

📥 Commits

Reviewing files that changed from the base of the PR and between de64a16 and 9dc1f9f.

📒 Files selected for processing (2)
  • src/services/aiAnalysisOptimizer.test.ts
  • src/services/aiAnalysisOptimizer.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/services/aiAnalysisOptimizer.ts

Comment thread src/services/aiAnalysisOptimizer.test.ts Outdated
Address CodeRabbit review: the pipelined-abort test previously relied on a
fixed 100ms delay and only asserted the README request was invoked. Await a
request-start promise instead, handle an already-aborted AbortSignal, and
assert the abort callback fired so the test proves the in-flight README
request was actually cancelled (not silently abandoned) after abort().
@AmintaCCCP
AmintaCCCP merged commit f8000af into main Aug 9, 2026
5 checks passed
@AmintaCCCP
AmintaCCCP deleted the feat/ai-rate-limit-handling branch August 9, 2026 17:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant