{"record":{"id":"fd365272a2183f15","repo":"dubinc/dub","slug":"errormessage","errorCode":null,"errorMessage":"errorMessage","messagePattern":"errorMessage","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/utils/src/functions/fetch-with-retry.ts","lineNumber":54,"sourceCode":"        continue;\n      }\n\n      // Handle unauthorized errors\n      if (response.status === 403) {\n        throw new Error(\"Unauthorized\");\n      }\n\n      // Handle other errors\n      if (!response.ok) {\n        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","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/dubinc/dub/blob/f216b94a24ca5a0a48c6543ee10392c9006c8b75/packages/utils/src/functions/fetch-with-retry.ts#L36-L72","documentation":"fetchWithRetry throws this Error when the server returns a non-OK, non-retryable HTTP status (anything other than 429, 5xx, or 403) on any attempt. It tries to parse the response body as JSON and use its `error` field as the message; if the body is not JSON, it falls back to `HTTP error <status>`. Unlike 429/5xx statuses, these failures are not retried — the error is thrown immediately.","triggerScenarios":"Any fetch via fetchWithRetry that gets a 4xx response such as 400 Bad Request, 401 Unauthorized, 404 Not Found, 409 Conflict, or 422 Unprocessable Entity from the target API.","commonSituations":"Calling an API with an expired or invalid access token (401), a mistyped URL or deleted resource (404), or a payload that fails server-side validation (400/422). Also common when hitting endpoints that don't return JSON, so the fallback `HTTP error 400` style message appears.","solutions":["Read the thrown message: if it's from the JSON body's `error` field, fix the API-level problem it names (bad token, bad payload).","If the message is `HTTP error <status>`, check the status code: 401/403 → refresh credentials; 404 → fix the URL/resource ID; 400/422 → fix the request body.","Log or inspect the full response before calling fetchWithRetry if you need the raw body, since this helper discards it.","Retry manually only after fixing the root cause — this class of error is deterministic and the library will not retry it."],"exampleFix":"// before\nconst res = await fetchWithRetry(url, { headers: { Authorization: `Bearer ${staleToken}` } });\n// after\nconst token = await getFreshToken();\nconst res = await fetchWithRetry(url, { headers: { Authorization: `Bearer ${token}` } });","handlingStrategy":"try-catch","validationCode":"// Optionally probe the URL/endpoint before calling\nconst res = await fetch(url, { method: 'HEAD' });\nif (res.status >= 400 && res.status !== 429 && res.status < 500) {\n  throw new Error(`Endpoint returned ${res.status}; fix request before retrying`);\n}","typeGuard":"function isHttpErrorMessage(e: unknown): e is Error & { message: string } {\n  return e instanceof Error && /^HTTP error \\d{3}/.test(e.message);\n}","tryCatchPattern":"try {\n  const res = await fetchWithRetry(url, init);\n} catch (e) {\n  if (isHttpErrorMessage(e)) {\n    const status = Number(e.message.match(/\\d{3}/)?.[0]);\n    // handle 4xx: refresh token (401/403), fix URL (404), fix payload (400/422)\n  } else {\n    throw e;\n  }\n}","preventionTips":["Validate request payloads and resource IDs before calling fetchWithRetry.","Refresh auth tokens proactively so 401/403 responses are rare.","Log request URLs; a 404 usually means a typo or deleted resource.","Remember 4xx (except 403) is never retried by this helper — don't rely on retries to mask bad requests."],"tags":["http","fetch","api-error","client-error"],"backgroundTag":"http-4xx-client-error","analyzedSha":"f216b94a24ca5a0a48c6543ee10392c9006c8b75","analyzedAt":"2026-08-31T18:35:50.395Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}