lobehub/lobehub · error · Error

Too many redirects while downloading binary

Error message

Too many redirects while downloading binary

What it means

Recursion guard inside downloadWithRedirects — decrements maxRedirects on each hop and throws when it reaches zero. The default cap is 5, mirroring the legacy scripts/download-agent-browser.mjs semantics. The error means the download URL entered a redirect loop or a chain longer than the cap; no body is written to dest.

Source

Thrown at apps/desktop/src/main/core/infrastructure/BinaryManager.ts:727

      }
    },
    manage,
    name,
    priority,
  };
}

// ========================================
// Internal helpers — download + Gatekeeper handling
// ========================================

/**
 * Follow HTTP(S) redirects and stream the body to `dest`. Mirrors the older
 * `scripts/download-agent-browser.mjs` semantics — 5 hops max, errors on
 * non-2xx, no checksum verification (left to the spec when needed).
 */
async function downloadWithRedirects(url: string, dest: string, maxRedirects = 5): Promise<void> {
  if (maxRedirects <= 0) throw new Error('Too many redirects while downloading binary');

  await new Promise<void>((resolve, reject) => {
    https
      .get(url, { headers: { 'User-Agent': 'lobehub-desktop-binary-manager' } }, (res) => {
        if (
          res.statusCode &&
          res.statusCode >= 300 &&
          res.statusCode < 400 &&
          res.headers.location
        ) {
          const next = res.headers.location;
          res.resume();
          downloadWithRedirects(next, dest, maxRedirects - 1).then(resolve, reject);
          return;
        }

        if (res.statusCode !== 200) {
          res.resume();

View on GitHub (pinned to 10f24d7ade)

Solutions

  1. Open the release URL in a browser with devtools open and count the 3xx hops — if it stabilises within 5, the issue is intermittent; if not, the URL is wrong.
  2. Update the BinarySpec.manage.release function to return the final asset URL (e.g. the browser_download_url from the GitHub API) instead of the redirecting short link.
  3. Raise the maxRedirects default in downloadWithRedirects if the CDN chain is legitimate and stable.
  4. Pin a different pinnedVersion whose asset URL is a direct download.
  5. If a corporate proxy is rewriting URLs, configure NO_PROXY/binaryManager to bypass it for the download host.

Example fix

// before
async function downloadWithRedirects(url: string, dest: string, maxRedirects = 5): Promise<void> {
  if (maxRedirects <= 0) throw new Error('Too many redirects while downloading binary');
  // ...
}

// after — resolve the final URL via HEAD preflight and surface the hop count when exceeded
async function resolveFinalUrl(startUrl: string, maxHops = 10): Promise<string> {
  let url = startUrl;
  for (let i = 0; i < maxHops; i++) {
    const res = await fetch(url, { method: 'HEAD', redirect: 'manual' });
    if (res.status >= 300 && res.status < 400 && res.headers.get('location')) {
      url = new URL(res.headers.get('location')!, url).toString();
      continue;
    }
    return url;
  }
  throw new Error(`Too many redirects while downloading binary (>${maxHops} hops)`);
}
async function downloadWithRedirects(url: string, dest: string): Promise<void> {
  const finalUrl = await resolveFinalUrl(url);
  // ...stream finalUrl to dest...
}
Defensive patterns

Strategy: retry

Validate before calling

import { fetch } from 'undici';

async function assertDownloadable(url: string, maxHops = 5): Promise<void> {
  let u = url;
  for (let i = 0; i < maxHops; i++) {
    const res = await fetch(u, { method: 'HEAD', redirect: 'manual' });
    if (res.status >= 300 && res.status < 400 && res.headers.get('location')) {
      u = new URL(res.headers.get('location')!, u).toString();
      continue;
    }
    if (res.status === 200) return;
    throw new Error(`Pre-flight: ${url} returned HTTP ${res.status}`);
  }
  throw new Error(`Pre-flight: ${url} exceeded ${maxHops} redirects`);
}

Type guard

function isRedirectLoopError(e: unknown): boolean {
  return e instanceof Error && /Too many redirects/.test(e.message);
}

Try / catch

try {
  await binaryManager.install(name);
} catch (e) {
  if (e instanceof Error && /Too many redirects/.test(e.message)) {
    // pin a different version whose asset URL is a direct download
    await binaryManager.install(name, fallbackPinnedVersion);
  } else throw e;
}

Prevention

When it happens

Trigger: A release URL that bounces between CDN nodes (e.g. GitHub releases → objects.githubusercontent.com → release-assets → ...); a misconfigured mirror that returns 302 to itself; an OAuth-gated URL that bounces through a login redirect; HTTPS-to-HTTP downgrade chains that re-upgrade.

Common situations: GitHub release asset behind multiple CDN hops on a slow region; an enterprise proxy that injects extra 302s for content inspection; a stale release() function in the BinarySpec returning the API URL instead of the direct asset URL; a tag rename that left the old URL redirecting forever.

Related errors


AI-assisted analysis of lobehub/lobehub@10f24d7ade (2026-08-12). Data as JSON: /api/errors/a9a3422d9af3089e. Report an issue: GitHub.