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
Problem
GraphQLClientandFileServicehave zero retry logic. Linear's API has rate limits and can return transient 5xx errors. Currently:This is especially painful for
issues createwhich 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():Retry behavior
Where to apply
GraphQLClient.request()— wrap therawRequestcallFileServicefetch calls — wrap eachfetch()Acceptance criteria
withRetry()utility with exponential backoff existsGraphQLClient.request()retries on 429/5xx (max 3 attempts)