[INT-API-005] Add retry logic for 429 rate limit responses

This commit is contained in:
senke 2025-12-26 09:10:19 +01:00
parent 6bb5cccdfa
commit d4733e8674
2 changed files with 31 additions and 18 deletions

View file

@ -560,7 +560,8 @@
"description": "Détecter les réponses 429 et retry avec backoff basé sur Retry-After header.",
"priority": "P2",
"priority_rank": 16,
"status": "todo",
"status": "completed",
"completed_at": "2025-01-27T15:30:00Z",
"estimated_hours": 1.5,
"side": "frontend_only",
"files_to_modify": [
@ -1097,13 +1098,13 @@
},
"progress_tracking": {
"total_tasks": 32,
"completed": 15,
"completed": 16,
"in_progress": 0,
"todo": 17,
"todo": 16,
"blocked": 0,
"completion_percentage": 47,
"last_updated": "2025-01-27T15:15:00Z",
"completion_percentage": 50,
"last_updated": "2025-01-27T15:30:00Z",
"estimated_completion_date": null,
"estimated_hours_remaining": 26.5
"estimated_hours_remaining": 25
}
}

View file

@ -607,20 +607,25 @@ apiClient.interceptors.response.use(
}
}
// Unified retry logic for all retryable errors
// INT-API-005: Unified retry logic for all retryable errors
const status = error.response?.status;
const retryCount = (originalRequest as any)?._retryCount || 0;
const maxRetries = DEFAULT_RETRY_CONFIG.maxRetries;
// INT-API-005: For 429 rate limit errors, use a specific max retries limit (3)
const isRateLimitError = status === 429;
const effectiveMaxRetries = isRateLimitError ? 3 : maxRetries; // Define here so it's accessible in both if blocks
// Check if error is retryable
if (isRetryableError(error, DEFAULT_RETRY_CONFIG) && originalRequest && retryCount < maxRetries) {
if (isRetryableError(error, DEFAULT_RETRY_CONFIG) && originalRequest && retryCount < effectiveMaxRetries) {
// For non-idempotent methods (POST, PUT, DELETE, PATCH), only retry on specific errors
const method = originalRequest.method?.toUpperCase();
const isIdempotent = isIdempotentMethod(method);
// For non-idempotent methods, only retry on network errors or 5xx errors (not 429)
if (!isIdempotent && status && status !== 500 && status !== 502 && status !== 503 && status !== 504) {
// Don't retry non-idempotent methods on client errors (except 5xx)
// INT-API-005: Allow retry for 429 rate limit errors even for non-idempotent methods
// For non-idempotent methods, only retry on network errors, 5xx errors, or 429 rate limit
if (!isIdempotent && status && status !== 429 && status !== 500 && status !== 502 && status !== 503 && status !== 504) {
// Don't retry non-idempotent methods on client errors (except 429 and 5xx)
const apiError = parseApiError(error);
return Promise.reject(apiError);
}
@ -628,40 +633,46 @@ apiClient.interceptors.response.use(
// Mark that we're retrying this request
(originalRequest as any)._retryCount = retryCount + 1;
// Calculate delay (respect Retry-After header if present, otherwise exponential backoff with jitter)
// INT-API-005: Calculate delay (respect Retry-After header if present for 429, otherwise exponential backoff with jitter)
// For 429 rate limit errors, getRetryDelay will use Retry-After header if present
const delay = getRetryDelay(error, retryCount, DEFAULT_RETRY_CONFIG.baseDelay, DEFAULT_RETRY_CONFIG.maxDelay);
// Log retry attempt with request_id if available
const apiError = parseApiError(error);
const errorType = status ? `HTTP ${status}` : error.code || 'Network Error';
// INT-API-005: Log retry attempt with appropriate max retries (3 for 429, default for others)
if (apiError.request_id) {
console.warn(
`[API Retry] ${errorType} error, retrying (${retryCount + 1}/${maxRetries}) - Request ID: ${apiError.request_id}`,
`[API Retry] ${errorType} error, retrying (${retryCount + 1}/${effectiveMaxRetries}) - Request ID: ${apiError.request_id}`,
{
status: status || 'N/A',
error_code: error.code || 'N/A',
retry_count: retryCount + 1,
max_retries: maxRetries,
max_retries: effectiveMaxRetries,
delay_ms: Math.round(delay),
request_id: apiError.request_id,
url: originalRequest?.url,
method: originalRequest?.method,
is_idempotent: isIdempotent,
is_rate_limit: isRateLimitError,
retry_after_header: error.response?.headers['retry-after'] || error.response?.headers['Retry-After'] || 'N/A',
},
);
} else {
console.warn(
`[API Retry] ${errorType} error, retrying (${retryCount + 1}/${maxRetries})`,
`[API Retry] ${errorType} error, retrying (${retryCount + 1}/${effectiveMaxRetries})`,
{
status: status || 'N/A',
error_code: error.code || 'N/A',
retry_count: retryCount + 1,
max_retries: maxRetries,
max_retries: effectiveMaxRetries,
delay_ms: Math.round(delay),
url: originalRequest?.url,
method: originalRequest?.method,
is_idempotent: isIdempotent,
is_rate_limit: isRateLimitError,
retry_after_header: error.response?.headers['retry-after'] || error.response?.headers['Retry-After'] || 'N/A',
},
);
}
@ -673,8 +684,9 @@ apiClient.interceptors.response.use(
});
}
// If already retried maxRetries times or error is not retryable, reject immediately
if (retryCount >= maxRetries) {
// INT-API-005: If already retried effectiveMaxRetries times or error is not retryable, reject immediately
// Reuse the same effectiveMaxRetries calculation from above
if (retryCount >= effectiveMaxRetries) {
const apiError = parseApiError(error);
const errorType = status ? `HTTP ${status}` : error.code || 'Network Error';