JuliusBrussee/caveman · error · TypeError

fetch failed: unexpected redirect

Error message

fetch failed: unexpected redirect

What it means

When the caller passes redirect: "error", the proxy-aware fetch treats any redirectable 3xx response as a hard failure instead of following it, throwing this TypeError. It mirrors the WHATWG fetch spec's 'error' redirect mode.

Source

Thrown at packages/cli/src/proxy-fetch.ts:353

      body: buffered,
      signal: request.signal ?? null,
    };
    let hop = resolveProxyUrl(current.url, env);

    for (let redirects = 0; ; redirects += 1) {
      const response = hop ? await sendThroughProxy(current, hop) : await baseFetch(current.url, {
        method: current.method,
        headers: current.headers as HeadersInit,
        body: asBodyInit(current.body),
        signal: current.signal,
        redirect: "manual",
      });

      const location = response.headers.get("location");
      const redirectable = [301, 302, 303, 307, 308].includes(response.status);
      if (!redirectable || !location || request.redirect === "manual") return response;
      if (redirects >= MAX_REDIRECTS) throw new TypeError("fetch failed: too many redirects");
      if (request.redirect === "error") throw new TypeError("fetch failed: unexpected redirect");

      const next = new URL(location, current.url);
      const method = nextMethod(response.status, current.method);
      const bodyDropped = method !== current.method;
      void response.body?.cancel();
      current = {
        method,
        url: next,
        headers: redirectedHeaders(current.headers, current.url, next, bodyDropped),
        body: bodyDropped ? null : current.body,
        signal: current.signal,
      };
      hop = resolveProxyUrl(next, env);
    }
  } as typeof fetch;
}

/**

View on GitHub (pinned to 5184b3d11a)

Solutions

  1. Update the request URL to the current (non-redirecting) endpoint.
  2. Remove redirect: "error" (default is "follow") if automatic redirect handling is acceptable.
  3. Follow the redirect manually with redirect: "manual" and inspect response.headers.get("location").
  4. Ask the server owner to stop redirecting that route if it should be stable.

Example fix

// before
await fetch("http://api.example.com/v1/things", { redirect: "error" }); // 301 to https
// after
await fetch("https://api.example.com/v1/things", { redirect: "error" }); // use the final URL
Defensive patterns

Strategy: try-catch

Type guard

function isUnexpectedRedirect(e: unknown): boolean { return e instanceof TypeError && e.message.includes("unexpected redirect"); }

Try / catch

try {
  const res = await fetch(url, { redirect: "error" });
} catch (e) {
  if (e instanceof TypeError && e.message.includes("unexpected redirect")) {
    console.error(`${url} redirected; update to the final URL or allow redirects`);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling fetch (installed by installProxyAwareFetch) with redirect: "error" while the server responds 301/302/303/307/308 with a Location header.

Common situations: Hardening-sensitive code that opts into redirect:"error" then hits an endpoint that has moved (http->https upgrade, trailing-slash redirect, renamed API route); stale bookmarks or config pointing at old URLs.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@5184b3d11a (2026-08-31). Data as JSON: /api/errors/43c322784b3775f2. Report an issue: GitHub.