dubinc/dub · error
errorMessage
Error message
errorMessage
What it means
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.
Source
Thrown at packages/utils/src/functions/fetch-with-retry.ts:54
continue;
}
// Handle unauthorized errors
if (response.status === 403) {
throw new Error("Unauthorized");
}
// Handle other errors
if (!response.ok) {
let errorMessage: string;
try {
const error = await response.json();
errorMessage = error.error || `HTTP error ${response.status}`;
} catch {
errorMessage = `HTTP error ${response.status}`;
}
console.error(`fetchWithRetry error: ${errorMessage}`);
throw new Error(errorMessage);
}
} catch (error) {
clearTimeout(timeoutId);
lastError = error instanceof Error ? error : new Error(String(error));
// If this is the last retry, throw the error
if (i === maxRetries - 1) {
const errMsg = `Failed after ${maxRetries} retries. Last error: ${lastError.message}`;
console.error(`fetchWithRetry error: ${errMsg}`);
throw new Error(errMsg);
}
// For network errors or timeouts, wait and retry
const delay = retryDelay + Math.pow(i, 2) * 50;
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
View on GitHub (pinned to f216b94a24)
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.
Example fix
// before
const res = await fetchWithRetry(url, { headers: { Authorization: `Bearer ${staleToken}` } });
// after
const token = await getFreshToken();
const res = await fetchWithRetry(url, { headers: { Authorization: `Bearer ${token}` } }); Defensive patterns
Strategy: try-catch
Validate before calling
// Optionally probe the URL/endpoint before calling
const res = await fetch(url, { method: 'HEAD' });
if (res.status >= 400 && res.status !== 429 && res.status < 500) {
throw new Error(`Endpoint returned ${res.status}; fix request before retrying`);
} Type guard
function isHttpErrorMessage(e: unknown): e is Error & { message: string } {
return e instanceof Error && /^HTTP error \d{3}/.test(e.message);
} Try / catch
try {
const res = await fetchWithRetry(url, init);
} catch (e) {
if (isHttpErrorMessage(e)) {
const status = Number(e.message.match(/\d{3}/)?.[0]);
// handle 4xx: refresh token (401/403), fix URL (404), fix payload (400/422)
} else {
throw e;
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of dubinc/dub@f216b94a24 (2026-08-31).
Data as JSON: /api/errors/fd365272a2183f15.
Report an issue: GitHub.