{"record":{"id":"8ab4743bb2740a44","repo":"dubinc/dub","slug":"failed-after-maxretries-retries-last-error","errorCode":null,"errorMessage":"Failed after ${maxRetries} retries. Last error: ${lastError.message}","messagePattern":"Failed after (.+?) retries\\. Last error: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/utils/src/functions/fetch-with-retry.ts","lineNumber":64,"sourceCode":"        let errorMessage: string;\n        try {\n          const error = await response.json();\n          errorMessage = error.error || `HTTP error ${response.status}`;\n        } catch {\n          errorMessage = `HTTP error ${response.status}`;\n        }\n        console.error(`fetchWithRetry error: ${errorMessage}`);\n        throw new Error(errorMessage);\n      }\n    } catch (error) {\n      clearTimeout(timeoutId);\n      lastError = error instanceof Error ? error : new Error(String(error));\n\n      // If this is the last retry, throw the error\n      if (i === maxRetries - 1) {\n        const errMsg = `Failed after ${maxRetries} retries. Last error: ${lastError.message}`;\n        console.error(`fetchWithRetry error: ${errMsg}`);\n        throw new Error(errMsg);\n      }\n\n      // For network errors or timeouts, wait and retry\n      const delay = retryDelay + Math.pow(i, 2) * 50;\n      await new Promise((resolve) => setTimeout(resolve, delay));\n    }\n  }\n\n  // This should never be reached due to the throw in the last retry,\n  // but TypeScript needs it for type safety\n  throw new Error(`Failed after ${maxRetries} retries`);\n}\n","sourceCodeStart":46,"sourceCodeEnd":77,"githubUrl":"https://github.com/dubinc/dub/blob/f216b94a24ca5a0a48c6543ee10392c9006c8b75/packages/utils/src/functions/fetch-with-retry.ts#L46-L77","documentation":"fetchWithRetry throws this Error when every one of the maxRetries attempts fails with a retryable condition — network failure, DNS error, abort due to timeout, or repeated 429/5xx responses. The original cause is preserved in the message as `Last error: ...`, so the final thrown error is a wrapper describing exhausted retries plus the last underlying failure.","triggerScenarios":"maxRetries consecutive attempts where fetch rejects (offline/DNS failure), the AbortController fires (response slower than `timeout`, default 5000ms), or the server keeps returning 429 or 5xx on every attempt.","commonSituations":"Calling an API that is down or deploying during an outage, running behind a firewall/VPN blocking the host, a slow endpoint exceeding the 5s default timeout 10 times in a row, or sustained rate limiting (429) that outlasts the backoff schedule.","solutions":["Read the `Last error:` suffix to identify the root cause (AbortError/timeout vs TypeError/network vs 429/5xx).","If timeouts dominate, raise the `timeout` option (e.g. timeout: 30000) so slow endpoints aren't aborted.","If 429/5xx dominate, increase `maxRetries` and `retryDelay`, or back off globally — the server may need minutes, not seconds.","Check basic connectivity to the host (curl/ping) and any proxy/firewall settings before re-running.","Wrap the call in your own try/catch and fall back to cached data or queue the work for later retry."],"exampleFix":"// before\nconst res = await fetchWithRetry(url); // default 5s timeout exhausts retries\n// after\nconst res = await fetchWithRetry(url, undefined, { timeout: 30000, maxRetries: 5, retryDelay: 2000 });","handlingStrategy":"retry","validationCode":"// Check reachability before the call\nconst host = new URL(input instanceof URL ? input.href : String(input)).host;\nconst reachable = await fetch(`https://${host}/`, { method: 'HEAD' }).then(r => r.ok).catch(() => false);\nif (!reachable) console.warn('Host unreachable; expect retries to exhaust');","typeGuard":"function isRetryExhaustionError(e: unknown): e is Error {\n  return e instanceof Error && /^Failed after \\d+ retries\\./.test(e.message);\n}","tryCatchPattern":"try {\n  const res = await fetchWithRetry(url, init, { timeout: 30000, maxRetries: 5, retryDelay: 2000 });\n} catch (e) {\n  if (isRetryExhaustionError(e)) {\n    // queue for later / serve cached data / alert on the underlying cause\n  } else {\n    throw e;\n  }\n}","preventionTips":["Tune timeout/maxRetries/retryDelay for slow or rate-limited endpoints.","Parse the `Last error:` suffix to distinguish timeouts from network failures from 429/5xx.","For sustained rate limits, add client-side throttling instead of burning retries.","Monitor the endpoint's health rather than relying on retries during outages."],"tags":["network","timeout","retry","fetch"],"backgroundTag":"max-retries-exceeded","analyzedSha":"f216b94a24ca5a0a48c6543ee10392c9006c8b75","analyzedAt":"2026-08-31T18:35:50.395Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}