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

  1. 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).
  2. 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.
  3. Log or inspect the full response before calling fetchWithRetry if you need the raw body, since this helper discards it.
  4. 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

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.