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

  1. Inspect the raw response with curl -sI <url> to confirm the redirect lacks a Location header and fix the server/proxy.
  2. Verify the release URL points at the expected host (GitHub releases), not a custom mirror emitting bad redirects.
  3. Disable any intercepting proxy/VPN or add its host to RELEASE_HOSTS if it legitimately serves releases.
  4. 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

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


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)