vercel/turborepo · error · Error

Failed to download: ${response.status}

Error message

Failed to download: ${response.status}

What it means

streamingExtract() in @turbo/utils fetches a tarball URL (used as the fallback path of downloadAndExtractExample, fetching https://codeload.github.com/vercel/turborepo/tar.gz/main) and throws with the HTTP status when the response is not ok or has no body. This path is reached when the primary git clone strategy fails.

Source

Thrown at packages/turbo-utils/src/examples.ts:346

    controller.abort();
  }, DOWNLOAD_TIMEOUT);

  // Track all write streams so we can clean them up on abort/error
  const writeStreams: Array<Writable> = [];

  // Pre-resolve root once for performance (avoids calling resolve() per entry)
  const resolvedRoot = resolve(root);

  // Cache created directories to avoid redundant mkdirSync calls
  const createdDirs = new Set<string>();

  try {
    const response = await fetch(url, {
      ...buildFetchInit(url),
      signal: controller.signal
    });
    if (!response.ok || !response.body) {
      throw new Error(`Failed to download: ${response.status}`);
    }

    const body = Readable.fromWeb(response.body as ReadableStream);
    let rootPath: string | null = null;

    // Track all file write operations so we can wait for them to complete
    const fileWritePromises: Array<Promise<void>> = [];

    const parser = new Parser({
      filter: (p: string) => {
        // Determine the unpacked root path dynamically instead of hardcoding.
        // This avoids issues when the repository has been renamed.
        if (rootPath === null) {
          const pathSegments = p.split("/");
          rootPath = pathSegments.length ? pathSegments[0] : null;
        }
        return filter(p, rootPath);
      },

View on GitHub (pinned to 9f94a7d215)

Solutions

  1. Wait and retry if the status is 429/403/5xx — GitHub rate limits are transient
  2. Check connectivity: curl -I https://codeload.github.com/vercel/turborepo/tar.gz/main
  3. Install git so the primary clone path succeeds and the tarball fallback is not needed
  4. Authenticate or use a personal access token / different network if rate limiting persists
Defensive patterns

Strategy: retry

Validate before calling

const ok = await fetch(url, { method: "HEAD" }).then((r) => r.ok).catch(() => false);
if (!ok) {
  // skip extraction, warn user, or switch to a mirror
}

Type guard

function isDownloadStatusError(e: unknown): boolean {
  return e instanceof Error && /^Failed to download: \d{3}$/.test(e.message);
}

Try / catch

for (const delay of [1000, 5000, 15000]) {
  try {
    await streamingExtract({ url, root, strip, filter });
    break;
  } catch (e) {
    if (e instanceof Error && e.message.startsWith("Failed to download:")) {
      await new Promise((r) => setTimeout(r, delay));
      continue;
    }
    throw e;
  }
}

Prevention

When it happens

Trigger: An HTTP >= 400 from codeload.github.com for the turborepo main tarball: 429/403 GitHub rate limiting, 5xx outages, or 404 if the branch name changes; also a 200 response with an empty body (response.body null), which some proxies produce.

Common situations: GitHub API/tarball rate limits hit from shared CI runners; environments where git is missing so the clone path always falls back to tarball; network appliances mangling streamed responses.

Related errors


AI-assisted analysis of vercel/turborepo@9f94a7d215 (2026-08-16). Data as JSON: /api/errors/e05a70d80838298b. Report an issue: GitHub.