Skip to content

feat: add retry with exponential backoff for transient API failures #62

@iamfj

Description

@iamfj

Problem

GraphQLClient and FileService have zero retry logic. Linear's API has rate limits and can return transient 5xx errors. Currently:

  • HTTP 429 (rate limit) → cryptic GraphQL error, no indication it's a rate limit
  • HTTP 5xx (server error) → one-shot failure, no retry
  • Network hiccups → immediate failure

This is especially painful for issues create which may have resolved 4-5 entity IDs via separate calls before the final mutation fails — all that work is lost.

Proposed solution

Add a shared retry utility and apply it to GraphQLClient.request():

// src/common/retry.ts
export async function withRetry<T>(
  fn: () => Promise<T>,
  options?: { maxRetries?: number; baseDelayMs?: number }
): Promise<T> {
  const { maxRetries = 3, baseDelayMs = 500 } = options ?? {};
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxRetries || !isRetryable(error)) throw error;
      const delay = baseDelayMs * 2 ** attempt;
      await new Promise((r) => setTimeout(r, delay));
    }
  }
  throw new Error("unreachable");
}

Retry behavior

Status Retry? Reason
429 Rate limited — wait and retry
500-599 Transient server error
401/403 Auth error — retrying won't help
400, 404 Client error — request is wrong
Network error Transient connectivity

Where to apply

  • GraphQLClient.request() — wrap the rawRequest call
  • FileService fetch calls — wrap each fetch()

Acceptance criteria

  • withRetry() utility with exponential backoff exists
  • GraphQLClient.request() retries on 429/5xx (max 3 attempts)
  • Auth errors are NOT retried
  • Retry delay follows exponential backoff (500ms, 1s, 2s)
  • Unit tests for retry logic (success after retry, exhausted retries, non-retryable errors)
  • Existing tests still pass

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions