Skip to content

fix(core): honor Retry-After hints and add a no-hint retry floor - #1233

Merged
murdore merged 1 commit into
juspay:releasefrom
morgan-coded:retry-after-1189
Jul 30, 2026
Merged

murdore merged 1 commit into
juspay:releasefrom
morgan-coded:retry-after-1189

Conversation

@morgan-coded

@morgan-coded morgan-coded commented Jul 24, 2026 •

Copy link
Copy Markdown
Contributor

withProviderRetry backed off exponentially at 1s then 2s, rather than the ~1s linear loop described in #1189; AI SDK errors reached it with responseHeaders intact, while Replicate submissions bypassed it and discarded those headers.

This extracts the existing Retry-After parser for numeric seconds, HTTP dates, and rate-limit fallbacks, uses server delays capped at 120s or a 10s no-hint floor in withProviderRetry, and carries Replicate's header through NeuroLinkError while retrying prediction submission, with polling behavior otherwise unchanged.

The 10s no-hint floor can delay real model-pool failover while a member retries internally — kept the floor the issue proposes, flagging that tradeoff for your call; the shape follows the existing ClientRateLimitError.retryAfter and accountPool.markQuotaExceeded(..., retryAfterMs) handling.

I added a focused offline suite with 9 passing tests, then ran format, source lint, validation, builds, CLI help, strict TypeScript, and all 23 unit legs locally; CI runs no test suites, and the only local failures were the five byte-matching baseline failures. Fixes #1189

Summary by CodeRabbit

  • Reliability Improvements
    • Improved automatic retries for provider requests by honoring server Retry-After and related rate-limit headers, with bounded minimum/maximum delays.
    • Enhanced rate-limit handling to reliably compute non-negative wait times from common headers.
    • Improved prediction submission/polling resilience by treating 429 and 5xx as retriable and propagating retry timing hints.
    • Extended error details to optionally include retryAfterMs for more precise retry behavior.
  • Tests
    • Added dedicated coverage for retry-delay parsing, clamping/bounding behavior, rate-limit handling, and prediction retry flows.
  • Chores
    • Added a focused retry-delay Vitest runner and included it in unit test runs.

Copilot AI review requested due to automatic review settings July 24, 2026 13:55
@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

@morgan-coded is attempting to deploy a commit to the Sachin Sharma's projects Team on Vercel.

A member of the Team first needs to authorize it.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@murdore, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 57 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9b5e1535-c3e4-492e-be96-40dc686b9ec4

📥 Commits

Reviewing files that changed from the base of the PR and between e7ec9fd and 6cf77e4.

📒 Files selected for processing (7)
  • package.json
  • src/lib/adapters/replicate/predictionLifecycle.ts
  • src/lib/mcp/httpRateLimiter.ts
  • src/lib/utils/errorHandling.ts
  • src/lib/utils/providerRetry.ts
  • src/lib/utils/retryAfter.ts
  • test/retryAfter.test.ts
📝 Walkthrough

Walkthrough

Retry timing is centralized through parseRetryAfterMs, propagated via NeuroLinkError, and consumed by provider retry logic. Replicate submission and polling now retry 429 responses with parsed delays, while HTTP rate limiting and unit tests use the shared behavior.

Changes

Retry-After provider flow

Layer / File(s) Summary
Retry timing contracts and parsing
src/lib/utils/retryAfter.ts, src/lib/utils/errorHandling.ts, src/lib/mcp/httpRateLimiter.ts
Adds shared Retry-After parsing, stores retryAfterMs on NeuroLinkError, and delegates HTTP rate-limit wait calculation to the parser.
Provider retry timing
src/lib/utils/providerRetry.ts
Recognizes retryable NeuroLinkError instances, honors bounded server delays, applies a 10-second fallback floor, supports optional spans, and injects sleeping.
Replicate prediction retries
src/lib/adapters/replicate/predictionLifecycle.ts
Wraps prediction submission in withProviderRetry, parses retry headers, handles submit timeouts, and marks 429 polling failures retriable.
Retry behavior validation and test wiring
test/retryAfter.test.ts, package.json
Tests parsing, delay floors and caps, fallback behavior, Replicate retries, and adds the suite to test:unit.

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

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant createPrediction
  participant withProviderRetry
  participant ReplicateAPI
  participant sleep
  Client->>createPrediction: create prediction
  createPrediction->>withProviderRetry: execute submission
  withProviderRetry->>ReplicateAPI: POST prediction
  ReplicateAPI-->>withProviderRetry: 429 with Retry-After
  withProviderRetry->>sleep: wait parsed delay
  withProviderRetry->>ReplicateAPI: retry submission
  ReplicateAPI-->>createPrediction: prediction result
  createPrediction-->>Client: return prediction
Loading

Suggested labels: released

Suggested reviewers: murdore

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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 summarizes the main change: honoring Retry-After hints with a minimum retry floor.
Linked Issues check ✅ Passed The changes satisfy #1189 by waiting on Retry-After/429 hints and using a ~10s floor when no hint is present.
Out of Scope Changes check ✅ Passed The added tests, retry parser reuse, and related rate-limit plumbing all support the retry-delay fix and appear in scope.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
test/retryAfter.test.ts (1)

39-64: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No coverage for httpRateLimiter.ts's rate-limit header handling or the X-RateLimit-* fallback paths.

This suite covers Retry-After numeric/date/absent/invalid cases and the Replicate/provider-retry paths well, but parseRetryAfterMs's X-RateLimit-Reset/X-RateLimit-Remaining branches and httpRateLimiter.ts#handleRateLimitResponse's refactor (a complexity-high change per the PR) have no direct test coverage in this new suite.

🤖 Prompt for 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.

In `@test/retryAfter.test.ts` around lines 39 - 64, The retry-after tests do not
cover the X-RateLimit fallback logic or the refactored handleRateLimitResponse
flow. Extend the Retry-After parsing suite to verify X-RateLimit-Reset and
X-RateLimit-Remaining behavior, including relevant missing or invalid headers,
and add direct tests for httpRateLimiter.ts#handleRateLimitResponse covering its
rate-limit response handling branches.
🤖 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/lib/utils/retryAfter.ts`:
- Around line 30-34: Update the numeric Retry-After handling in retryAfter so
the seconds-to-milliseconds result is clamped to zero with the same behavior
used by the HTTP-date and X-RateLimit-Reset branches. Preserve the existing
parsing and fallback flow, including the Number.isNaN check.

---

Nitpick comments:
In `@test/retryAfter.test.ts`:
- Around line 39-64: The retry-after tests do not cover the X-RateLimit fallback
logic or the refactored handleRateLimitResponse flow. Extend the Retry-After
parsing suite to verify X-RateLimit-Reset and X-RateLimit-Remaining behavior,
including relevant missing or invalid headers, and add direct tests for
httpRateLimiter.ts#handleRateLimitResponse covering its rate-limit response
handling branches.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 47ceb20a-9a94-405e-9dec-327645d88d08

📥 Commits

Reviewing files that changed from the base of the PR and between c595151 and 0cbba06.

📒 Files selected for processing (7)
  • package.json
  • src/lib/adapters/replicate/predictionLifecycle.ts
  • src/lib/mcp/httpRateLimiter.ts
  • src/lib/utils/errorHandling.ts
  • src/lib/utils/providerRetry.ts
  • src/lib/utils/retryAfter.ts
  • test/retryAfter.test.ts

Comment thread src/lib/utils/retryAfter.ts
@murdore

murdore commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

@morgan-coded can you resolve conflicts?

@morgan-coded

Copy link
Copy Markdown
Contributor Author

Resolved the conflict by taking the 10.6.4 version bump and keeping both test:agent-runtime:vitest and test:retry-after:vitest in the unit-test chain.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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/lib/utils/providerRetry.ts`:
- Around line 146-156: Update the retry delay calculation around getRetryAfterMs
and boundedRetryAfterMs to treat non-finite retryAfterMs values, including NaN
and infinities, as absent hints. Validate finiteness before clamping so the
nullish fallback applies NO_HINT_FLOOR_MS (or the exponential delay) rather than
propagating NaN or an invalid bounded value.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8e6513b5-15cf-4117-ab40-715f4504ad9d

📥 Commits

Reviewing files that changed from the base of the PR and between 5aa19bf and e7ec9fd.

📒 Files selected for processing (7)
  • package.json
  • src/lib/adapters/replicate/predictionLifecycle.ts
  • src/lib/mcp/httpRateLimiter.ts
  • src/lib/utils/errorHandling.ts
  • src/lib/utils/providerRetry.ts
  • src/lib/utils/retryAfter.ts
  • test/retryAfter.test.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/lib/utils/errorHandling.ts
  • src/lib/utils/retryAfter.ts
  • test/retryAfter.test.ts
  • package.json
  • src/lib/mcp/httpRateLimiter.ts
  • src/lib/adapters/replicate/predictionLifecycle.ts

Comment on lines +146 to +156
const retryAfterMs = getRetryAfterMs(error);
const boundedRetryAfterMs =
retryAfterMs === undefined
? undefined
: Math.min(MAX_RETRY_AFTER_MS, Math.max(0, retryAfterMs));
const delay =
boundedRetryAfterMs ??
Math.max(BASE_RETRY_DELAY_MS * Math.pow(2, attempt), NO_HINT_FLOOR_MS);

// Record retry event on the OTel span
span.addEvent("gen_ai.provider.retry", {
span?.addEvent("gen_ai.provider.retry", {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Reject non-finite retry metadata before clamping.

If retryAfterMs is NaN, the clamp also becomes NaN; the default timer then retries immediately instead of applying NO_HINT_FLOOR_MS. Treat non-finite values as absent hints.

Proposed fix
       const retryAfterMs = getRetryAfterMs(error);
       const boundedRetryAfterMs =
-        retryAfterMs === undefined
-          ? undefined
+        retryAfterMs !== undefined && Number.isFinite(retryAfterMs)
+          ? Math.min(MAX_RETRY_AFTER_MS, Math.max(0, retryAfterMs))
           : undefined;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const retryAfterMs = getRetryAfterMs(error);
const boundedRetryAfterMs =
retryAfterMs === undefined
? undefined
: Math.min(MAX_RETRY_AFTER_MS, Math.max(0, retryAfterMs));
const delay =
boundedRetryAfterMs ??
Math.max(BASE_RETRY_DELAY_MS * Math.pow(2, attempt), NO_HINT_FLOOR_MS);
// Record retry event on the OTel span
span.addEvent("gen_ai.provider.retry", {
span?.addEvent("gen_ai.provider.retry", {
const retryAfterMs = getRetryAfterMs(error);
const boundedRetryAfterMs =
retryAfterMs !== undefined && Number.isFinite(retryAfterMs)
? Math.min(MAX_RETRY_AFTER_MS, Math.max(0, retryAfterMs))
: undefined;
const delay =
boundedRetryAfterMs ??
Math.max(BASE_RETRY_DELAY_MS * Math.pow(2, attempt), NO_HINT_FLOOR_MS);
// Record retry event on the OTel span
span?.addEvent("gen_ai.provider.retry", {
🤖 Prompt for 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.

In `@src/lib/utils/providerRetry.ts` around lines 146 - 156, Update the retry
delay calculation around getRetryAfterMs and boundedRetryAfterMs to treat
non-finite retryAfterMs values, including NaN and infinities, as absent hints.
Validate finiteness before clamping so the nullish fallback applies
NO_HINT_FLOOR_MS (or the exponential delay) rather than propagating NaN or an
invalid bounded value.

@murdore
murdore force-pushed the retry-after-1189 branch from e7ec9fd to 6cf77e4 Compare July 30, 2026 15:48
@murdore
murdore merged commit f12984c into juspay:release Jul 30, 2026
8 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 10.8.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Video generation retries ignore Replicate's retry_after — burst-throttled accounts fail every request

3 participants