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
- 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.
- 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.
- Raise the maxRedirects default in downloadWithRedirects if the CDN chain is legitimate and stable.
- Pin a different pinnedVersion whose asset URL is a direct download.
- 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
- Pre-flight HEAD the release URL to detect redirect loops before streaming.
- Pin BinarySpec.manage.release to return the final asset URL (e.g. browser_download_url) instead of a short link.
- Bypass corporate proxies for the download host via NO_PROXY if they inject extra redirects.
- Bump maxRedirects in downloadWithRedirects if your CDN chain is legitimately longer than 5.
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
- Binary '${name}' is not available on the system PATH and doe
- Failed to get agent forks: ${response.statusText}
- Failed to deprecate agent group: ${response.statusText}
- Failed to fork agent group: ${response.statusText}
- Failed to get agent group fork source: ${response.statusText
AI-assisted analysis of lobehub/lobehub@10f24d7ade (2026-08-12).
Data as JSON: /api/errors/a9a3422d9af3089e.
Report an issue: GitHub.