Skip to content

Comments

feat: add llm retry helper#173

Merged
gentlementlegen merged 7 commits intodevelopmentfrom
feat/llm-retry
Feb 2, 2026
Merged

feat: add llm retry helper#173
gentlementlegen merged 7 commits intodevelopmentfrom
feat/llm-retry

Conversation

@gentlementlegen
Copy link
Member

@gentlementlegen gentlementlegen commented Jan 14, 2026

Summary

  • add shared LLM retryable detection with retry-after parsing
  • add tests covering LLM retry helper scenarios

Resolves #170

@coderabbitai
Copy link

coderabbitai bot commented Jan 14, 2026

📝 Walkthrough

Walkthrough

Adds LLM-aware retry enhancements: a new exported function checkLlmRetryableState(error): boolean | number, an optional isErrorRetryable callback on RetryOptions, extensive parsing utilities for status, headers, and Retry-After values (including ms parsing), refactors retry handling, updates tests to cover these cases, and adds the ms dependency and a cspell word.

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% 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
Title check ✅ Passed The title clearly and concisely describes the main change: adding an LLM retry helper function.
Description check ✅ Passed The description provides a brief summary and correctly links to issue #170 using the required 'Resolves' keyword, meeting template requirements.
Linked Issues check ✅ Passed All coding requirements from issue #170 are met: checkLlmRetryableState() is exported, retry-after parsing is implemented [170], and header handling is included [170].
Out of Scope Changes check ✅ Passed All changes align with issue #170: retry helper migration, header parsing, and test coverage. The ms dependency and .cspell.json update support the retry implementation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/llm-retry

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 and usage tips.

@gentlementlegen gentlementlegen marked this pull request as ready for review January 25, 2026 11:22
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/helpers/retry.ts (1)

16-41: Avoid sleeping after the final attempt.

The loop still sleeps (including long retry-after delays) even when maxRetries is exhausted, which unnecessarily stalls failure. Guard against delays once the last attempt has failed.

🐛 Proposed fix
-      if (shouldRetry === false) {
-        throw lastError;
-      } else if (typeof shouldRetry === "number" && Number.isFinite(shouldRetry)) {
-        await sleep(shouldRetry);
-      } else {
-        await sleep(delay);
-        delay *= 2;
-      }
+      if (shouldRetry === false || i >= options.maxRetries) {
+        throw lastError;
+      }
+      if (typeof shouldRetry === "number" && Number.isFinite(shouldRetry)) {
+        await sleep(shouldRetry);
+      } else {
+        await sleep(delay);
+        delay *= 2;
+      }

Ensures retries stop when max attempts are reached.
@gentlementlegen
Copy link
Member Author

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3df76f0cef

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 83 to 87
const maybeError = source as { status?: unknown; statusCode?: unknown; response?: { status?: unknown } };
const candidates: unknown[] = [maybeError.status, maybeError.statusCode, maybeError.response?.status];
for (const candidate of candidates) {
if (typeof candidate === "number") return candidate;
}

Choose a reason for hiding this comment

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

P2 Badge Parse string HTTP status codes for retry decisions

In getStatusFromSource, only numeric status/statusCode/response.status values are recognized. If a client surfaces these as strings (e.g. { status: "429" } or { statusCode: "503" }), checkLlmRetryableState will treat the error as non-retryable unless the message also contains a code, so transient 429/5xx responses won’t be retried. Consider accepting numeric strings (similar to getErrorStatus in src/error.ts) or normalizing these fields before the retry decision.

Useful? React with 👍 / 👎.

Copy link
Member Author

Choose a reason for hiding this comment

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

Valid point — getStatusFromSource was only checking for numeric status values while getErrorStatus in src/error.ts already handled string status codes. Fixed in 29c4d32 by parsing string candidates with Number.parseInt, matching the existing pattern.

Align getStatusFromSource with getErrorStatus by also parsing string
status/statusCode values so that errors like { status: "429" } are
correctly recognized as retryable.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Comment on lines +52 to +72
// eslint-disable-next-line sonarjs/function-return-type
export function checkLlmRetryableState(error: unknown): boolean | number {
if (error instanceof SyntaxError || error instanceof TypeBoxError || error instanceof LogReturn) {
return true;
}

const status = extractStatus(error) ?? extractStatusFromMessage(error);
if (!status) return false;

if (status === 429) {
const headers = extractHeaders(error);
const tokenDelay = parseRetryAfter(headers, "x-ratelimit-reset-tokens");
const requestDelay = parseRetryAfter(headers, "x-ratelimit-reset-requests");
const retryAfter = parseRetryAfter(headers, "retry-after");
const delays = [tokenDelay, requestDelay, retryAfter].filter((value): value is number => Number.isFinite(value));
if (!delays.length) return true;
return Math.max(...delays);
}

return status >= 500;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid treating status 0 as “missing.”
if (!status) will treat 0 as absent; some clients use 0 for network errors, which may deserve a retry. Consider a null/undefined check instead.

💡 Proposed fix
-  if (!status) return false;
+  if (status == null) return false;
📝 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
// eslint-disable-next-line sonarjs/function-return-type
export function checkLlmRetryableState(error: unknown): boolean | number {
if (error instanceof SyntaxError || error instanceof TypeBoxError || error instanceof LogReturn) {
return true;
}
const status = extractStatus(error) ?? extractStatusFromMessage(error);
if (!status) return false;
if (status === 429) {
const headers = extractHeaders(error);
const tokenDelay = parseRetryAfter(headers, "x-ratelimit-reset-tokens");
const requestDelay = parseRetryAfter(headers, "x-ratelimit-reset-requests");
const retryAfter = parseRetryAfter(headers, "retry-after");
const delays = [tokenDelay, requestDelay, retryAfter].filter((value): value is number => Number.isFinite(value));
if (!delays.length) return true;
return Math.max(...delays);
}
return status >= 500;
}
// eslint-disable-next-line sonarjs/function-return-type
export function checkLlmRetryableState(error: unknown): boolean | number {
if (error instanceof SyntaxError || error instanceof TypeBoxError || error instanceof LogReturn) {
return true;
}
const status = extractStatus(error) ?? extractStatusFromMessage(error);
if (status == null) return false;
if (status === 429) {
const headers = extractHeaders(error);
const tokenDelay = parseRetryAfter(headers, "x-ratelimit-reset-tokens");
const requestDelay = parseRetryAfter(headers, "x-ratelimit-reset-requests");
const retryAfter = parseRetryAfter(headers, "retry-after");
const delays = [tokenDelay, requestDelay, retryAfter].filter((value): value is number => Number.isFinite(value));
if (!delays.length) return true;
return Math.max(...delays);
}
return status >= 500;
}

Copy link
Member

@0x4007 0x4007 left a comment

Choose a reason for hiding this comment

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

Cool seems good assuming this works

@gentlementlegen
Copy link
Member Author

@0x4007 Once this is published, I already drafted all the pull-requests to update the plugins and use this, so you'll be able to test it.

@gentlementlegen gentlementlegen merged commit 2b6d1cc into development Feb 2, 2026
5 checks passed
@gentlementlegen gentlementlegen deleted the feat/llm-retry branch February 2, 2026 06:51
@ubiquity-os-beta
Copy link
Contributor

ubiquity-os-beta bot commented Feb 2, 2026

 [ 5.37 UUSD ] 

@gentlementlegen
Contributions Overview
ViewContributionCountReward
ReviewSpecification15.37
ReviewComment30
Conversation Incentives
CommentFormattingRelevancePriorityReward
## Summary- add shared LLM retryable detection with retry-afte…
2.21
content:
  content:
    h2:
      score: 1
      elementCount: 1
    ul:
      score: 0
      elementCount: 1
    li:
      score: 0.1
      elementCount: 2
    p:
      score: 0
      elementCount: 1
  result: 1.2
regex:
  wordCount: 19
  wordValue: 0.1
  result: 1.01
authorship: 1
Relevance: 1
Readability: 31.7
15.37
@codex review
0
content:
  content:
    p:
      score: 0
      elementCount: 1
  result: 0
regex:
  wordCount: 2
  wordValue: 0
  result: 0
authorship: 1
Relevance: 0
Readability: 35.6
20
@0x4007 Once this is published, I already drafted all the pull-r…
0
content:
  content:
    p:
      score: 0
      elementCount: 1
  result: 0
regex:
  wordCount: 27
  wordValue: 0
  result: 0
authorship: 1
Relevance: 0.5
Readability: 72.9
20
Valid point — `getStatusFromSource` was only checking fo…
0
content:
  content:
    p:
      score: 0
      elementCount: 1
  result: 0
regex:
  wordCount: 28
  wordValue: 0
  result: 0
authorship: 1
Relevance: 1
Readability: 53.6
20

 [ 3.96 UUSD ] 

@0x4007
Contributions Overview
ViewContributionCountReward
ReviewCode Review13.96
ReviewComment10
Review Details for #173#issue-3172551780
ChangesPriorityReward
+180 -1823.96
Conversation Incentives
CommentFormattingRelevancePriorityReward
Cool seems good assuming this works
0.43
content:
  content:
    p:
      score: 0
      elementCount: 1
  result: 0
regex:
  wordCount: 6
  wordValue: 0.1
  result: 0.43
authorship: 1
Relevance: 0
Readability: 87.9
20

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.

Migrate checkLlmRetryableState()

2 participants