Hmbown/CodeWhale · error
Release redirect without a location
Error message
Release redirect without a location
What it means
fetchReleaseWeb manually follows release-download redirects and only accepts hops landing on a whitelisted RELEASE_HOSTS https host. When a 3xx response carries no Location header there is nothing to follow, so the fetcher aborts rather than looping or returning a redirect response. It guards against malformed servers and redirect-based attacks.
Solutions
- Inspect the raw response with curl -sI <url> to confirm the redirect lacks a Location header and fix the server/proxy.
- Verify the release URL points at the expected host (GitHub releases), not a custom mirror emitting bad redirects.
- Disable any intercepting proxy/VPN or add its host to RELEASE_HOSTS if it legitimately serves releases.
- Retry later if GitHub is having an incident; otherwise fall back to the GitHub API resolution path.
Example fix
// before
const res = await fetchReleaseWeb(releaseUrl);
// after
let res;
try { res = await fetchReleaseWeb(releaseUrl); }
catch (e) { if (String(e.message).includes('without a location')) res = await fetchReleaseViaApi(); else throw e; } Defensive patterns
Strategy: fallback
Validate before calling
const res = await fetch(url, { method: 'HEAD', redirect: 'manual' });
if ([301,302,307,308].includes(res.status) && !res.headers.get('location')) console.warn('redirect without location; use API fallback'); Try / catch
try { download() } catch (e) { if (String(e.message).includes('Release redirect')) return fetchViaGitHubApi(); throw e; } Prevention
- Probe release URLs with a HEAD request before scripted downloads.
- Avoid routing release downloads through proxies/CDNs that mangle redirects.
- Keep a non-redirect fallback resolution path (GitHub API) wired in.
When it happens
Trigger: A request to a release URL returns status 301/302/307/308 but response.headers.get('location') is null.
Common situations: Misconfigured CDN/proxy in front of the release host emitting bare 3xx; captive portals returning redirects without Location; GitHub returning an unusual redirect shape during incidents; an intercepted HTTPS proxy stripping headers.
Related errors
- bundle redirects may not change URL scheme
- failed to resolve latest stable release from
- too many redirects starting at
- Too many redirects while checking
- Too many redirects while downloading
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/afb492aa20f4b1f8.
Report an issue: GitHub.
Appendix: source
Thrown at web/lib/computer-use-release.ts:117
if (done) break;
length += value.byteLength;
if (length > limit) throw new Error("Release response exceeds size limit");
text += decoder.decode(value, { stream: true });
}
return JSON.parse(text + decoder.decode());
} finally { await reader.cancel(); reader.releaseLock(); }
}
const WEB_HEADERS = { "User-Agent": "codewhale-web" };
/** GET a release web endpoint, following at most three 302s and only onto GitHub's release hosts over https. */
async function fetchReleaseWeb(url: string): Promise<Response> {
for (let hops = 0; ; hops++) {
const response = await fetch(url, { redirect: "manual", headers: WEB_HEADERS, signal: AbortSignal.timeout(5000) });
if (![301, 302, 307, 308].includes(response.status)) return response;
await response.body?.cancel();
const location = response.headers.get("location");
if (!location) throw new Error("Release redirect without a location");
const target = new URL(location, url);
if (hops >= 3 || target.protocol !== "https:" || !RELEASE_HOSTS.has(target.hostname)) {
throw new Error(`Release redirect refused: ${target.hostname}`);
}
url = target.href;
}
}
/** Resolve the download without the GitHub API: read the latest receipt from the
* release web endpoint, then confirm GitHub serves the archive the receipt names. */
async function receiptQualifiedRelease(): Promise<ComputerUseRelease> {
try {
const response = await fetchReleaseWeb(`${COMPUTER_USE_REPO}/releases/latest/download/release.json`);
if (response.status === 404) return { status: "pending" };
if (!response.ok) return { status: "unavailable" };
const receipt = record(await boundedJson(response, 16 * 1024));
const version = typeof receipt.version === "string" && /^\d+\.\d+\.\d+$/.test(receipt.version) ? receipt.version : null;
const archive = `Codewhale-Computer-Use-${version}-macos-universal.zip`;View on GitHub (pinned to 433685b202)