LLM provider SDK lacks retry logic for transient API errors #8275

Open
opened 2026-02-16 18:09:34 -05:00 by yindo · 1 comment
Owner

Originally created by @riftzen-bit on GitHub (Feb 1, 2026).

Originally assigned to: @thdxr on GitHub.

Summary

The LLM provider integration lacks retry logic for transient API errors (rate limits, temporary failures), treating all errors as permanent failures.

Location

packages/opencode/src/provider/provider.ts (overall architecture)
packages/opencode/src/provider/sdk/copilot/openai-compatible-error.ts

Issue

The provider SDK:

  1. Has no exponential backoff for 429 (rate limit) responses
  2. Doesn't parse or respect `Retry-After` headers
  3. Treats all API errors as permanent failures
  4. Has no retry budget or max attempts configuration
  5. Error structures don't implement `isRetryable` checks
// Current error handling doesn't distinguish retryable from permanent errors
const errorStructure = config.errorStructure ?? defaultOpenAICompatibleErrorStructure

Impact

  • Severity: Medium
  • Type: Reliability
  • Effect:
    • Rate limit errors immediately fail instead of backing off
    • Temporary network issues cause permanent failures
    • Higher error rates during API instability

Suggested Fix

  1. Add retry configuration to provider options:
interface ProviderRetryConfig {
  maxAttempts?: number
  initialDelayMs?: number
  maxDelayMs?: number
  retryableStatusCodes?: number[]
}
  1. Implement exponential backoff with jitter:
async function fetchWithRetry(url, options, retryConfig) {
  let attempt = 0
  while (attempt < retryConfig.maxAttempts) {
    try {
      const response = await fetch(url, options)
      if (response.status === 429 || response.status >= 500) {
        const retryAfter = response.headers.get('Retry-After')
        const delay = retryAfter 
          ? parseInt(retryAfter) * 1000 
          : calculateBackoff(attempt, retryConfig)
        await sleep(delay)
        attempt++
        continue
      }
      return response
    } catch (e) {
      if (!isRetryableError(e) || attempt >= retryConfig.maxAttempts - 1) throw e
      await sleep(calculateBackoff(attempt, retryConfig))
      attempt++
    }
  }
}
  1. Add `isRetryable` to error structures to distinguish between:
    • Rate limit (429) - retry with backoff
    • Server error (5xx) - retry with backoff
    • Client error (4xx except 429) - don't retry
    • Network error - retry with backoff
Originally created by @riftzen-bit on GitHub (Feb 1, 2026). Originally assigned to: @thdxr on GitHub. ## Summary The LLM provider integration lacks retry logic for transient API errors (rate limits, temporary failures), treating all errors as permanent failures. ## Location `packages/opencode/src/provider/provider.ts` (overall architecture) `packages/opencode/src/provider/sdk/copilot/openai-compatible-error.ts` ## Issue The provider SDK: 1. Has no exponential backoff for 429 (rate limit) responses 2. Doesn't parse or respect \`Retry-After\` headers 3. Treats all API errors as permanent failures 4. Has no retry budget or max attempts configuration 5. Error structures don't implement \`isRetryable\` checks ```typescript // Current error handling doesn't distinguish retryable from permanent errors const errorStructure = config.errorStructure ?? defaultOpenAICompatibleErrorStructure ``` ## Impact - **Severity**: Medium - **Type**: Reliability - **Effect**: - Rate limit errors immediately fail instead of backing off - Temporary network issues cause permanent failures - Higher error rates during API instability ## Suggested Fix 1. Add retry configuration to provider options: ```typescript interface ProviderRetryConfig { maxAttempts?: number initialDelayMs?: number maxDelayMs?: number retryableStatusCodes?: number[] } ``` 2. Implement exponential backoff with jitter: ```typescript async function fetchWithRetry(url, options, retryConfig) { let attempt = 0 while (attempt < retryConfig.maxAttempts) { try { const response = await fetch(url, options) if (response.status === 429 || response.status >= 500) { const retryAfter = response.headers.get('Retry-After') const delay = retryAfter ? parseInt(retryAfter) * 1000 : calculateBackoff(attempt, retryConfig) await sleep(delay) attempt++ continue } return response } catch (e) { if (!isRetryableError(e) || attempt >= retryConfig.maxAttempts - 1) throw e await sleep(calculateBackoff(attempt, retryConfig)) attempt++ } } } ``` 3. Add \`isRetryable\` to error structures to distinguish between: - Rate limit (429) - retry with backoff - Server error (5xx) - retry with backoff - Client error (4xx except 429) - don't retry - Network error - retry with backoff
Author
Owner

@github-actions[bot] commented on GitHub (Feb 1, 2026):

This issue might be a duplicate of existing issues. Please check:

  • #1712: Implement exponential back-off when hitting rate limits (original request for exponential backoff on rate limits)
  • #9038: Provider 520 errors surface as MessageAbortedError with no diagnostics (zero tokens, repeated retries) (related to error handling and retry classification)
  • #8769: [FEATURE]: Retry: allow user to change "exponential backoff" to "fixed interval" (addresses limitations of current exponential backoff implementation)
  • #10725: [FEATURE]: automatic model switch upon rate limitation (related feature for handling rate limits)
  • #3011: [Feature Request] Configurable Retry Mechanism via opencode.json (requests configurable retry settings)

Feel free to ignore if none of these address your specific case.

@github-actions[bot] commented on GitHub (Feb 1, 2026): This issue might be a duplicate of existing issues. Please check: - #1712: Implement exponential back-off when hitting rate limits (original request for exponential backoff on rate limits) - #9038: Provider 520 errors surface as MessageAbortedError with no diagnostics (zero tokens, repeated retries) (related to error handling and retry classification) - #8769: [FEATURE]: Retry: allow user to change "exponential backoff" to "fixed interval" (addresses limitations of current exponential backoff implementation) - #10725: [FEATURE]: automatic model switch upon rate limitation (related feature for handling rate limits) - #3011: [Feature Request] Configurable Retry Mechanism via opencode.json (requests configurable retry settings) Feel free to ignore if none of these address your specific case.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#8275