jdx/mise · error

too many redirects

Error message

too many redirects

What it means

The relay deliberately follows at most one download redirect: it re-sends the request to the redirect target once, and if the resulting response is still a 3xx it aborts with "too many redirects" instead of following further. This bounds the redirect chain so the relay never proxies open-redirect chains or loops.

Source

Thrown at src/github_relay.rs:685

                || !(asset_redirect(&url) || archive_redirect(&url, target.archive_repo.as_deref()))
            {
                bail!("unsupported redirect");
            }
            let redirect_host = url.host_str().unwrap_or_default().to_string();
            let redirected_at = std::time::Instant::now();
            let mut redirected = broker.client.request(parts.method.clone(), url);
            for name in ["range", "if-range"] {
                if let Some(value) = parts.headers.get(name) {
                    redirected = redirected.header(name, value);
                }
            }
            response = redirected.send().await?;
            if broker.audit.options.log_requests {
                broker.audit.emit(serde_json::json!({"event": "request", "operation": format!("{} {redirect_host}/<download>", parts.method), "status": response.status().as_u16(), "headers_ms": redirected_at.elapsed().as_millis()}));
            }
        }
        if response.status().is_redirection() {
            bail!("too many redirects");
        }
        let mut builder = Response::builder().status(response.status());
        for name in [
            "content-type",
            "content-length",
            "content-range",
            "etag",
            "last-modified",
            "link",
        ] {
            if let Some(value) = response.headers().get(name) {
                builder = builder.header(name, value);
            }
        }
        let stream = futures_util::stream::try_unfold(
            (response, permit, broker.cancel, broker.audit, deadline),
            |(mut response, permit, cancel, audit, deadline)| async move {
                let chunk = tokio::select! {

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Retry the download so the relay obtains a fresh short-lived redirect link (stale links can bounce).
  2. Re-authenticate / refresh credentials if the redirect chain suggests an auth-driven redirect loop.
  3. Fetch the asset URL directly in the client if your path requires multi-hop redirects the relay intentionally won't follow.
Defensive patterns

Strategy: retry

Try / catch

match result {
    Err(e) if e.to_string().contains("too many redirects") =>
        retry_with_fresh_link(max_attempts = 2),
    other => other,
}

Prevention

When it happens

Trigger: The post-redirect response itself carries a Location header (another 3xx status) — e.g. the asset host redirecting again (CDN hop chains), redirect loops, or an auth-expired redirect bouncing between hosts.

Common situations: GitHub asset hosts adding an extra CDN hop the relay's single-hop policy can't follow; expired/rotated short-lived archive links bouncing back to the API; misconfigured proxies injecting extra redirects.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/e3e500d213540d8d. Report an issue: GitHub.