nextlevelbuilder/ui-ux-pro-max-skill · error · GitHubDownloadError

Failed to download: ${response.status} ${response.statusText

Error message

Failed to download: ${response.status} ${response.statusText}

What it means

downloadRelease() streams a release asset to disk; after checkRateLimit(), any non-2xx becomes GitHubDownloadError('Failed to download: <status> <reason>'). Because it sends 'Accept: application/octet-stream' plus auth headers to the asset URL, common causes are 404 (asset deleted or URL expired) and 401/403 (token invalid or lacking access to a private asset). The asset URL from the API can also go stale after re-uploading assets.

Source

Thrown at cli/src/utils/github.ts:106

    throw new GitHubDownloadError(`Failed to fetch latest release: ${response.status} ${response.statusText}`);
  }

  return response.json();
}

export async function downloadRelease(url: string, dest: string, token?: string): Promise<void> {
  const response = await fetch(url, {
    headers: {
      'User-Agent': USER_AGENT,
      'Accept': 'application/octet-stream',
      ...getAuthHeaders(token),
    },
  });

  checkRateLimit(response);

  if (!response.ok) {
    throw new GitHubDownloadError(`Failed to download: ${response.status} ${response.statusText}`);
  }

  const buffer = await response.arrayBuffer();
  await writeFile(dest, Buffer.from(buffer));
}

export function getAssetUrl(release: Release): string | null {
  // First try to find an uploaded ZIP asset
  const asset = release.assets.find(a => a.name.endsWith('.zip'));
  if (asset?.browser_download_url) {
    return asset.browser_download_url;
  }

  // Fall back to GitHub's auto-generated archive
  // Format: https://github.com/{owner}/{repo}/archive/refs/tags/{tag}.zip
  if (release.tag_name) {
    return `https://github.com/${REPO_OWNER}/${REPO_NAME}/archive/refs/tags/${release.tag_name}.zip`;
  }

View on GitHub (pinned to a38d04c3d5)

Solutions

  1. Re-fetch the release (getLatestRelease) immediately before downloading so the asset URL is fresh, then retry.
  2. Confirm the asset still exists on the release page; if re-uploaded, invalidate any cached release JSON.
  3. Verify the token is valid and has read access to the repo if the asset is private (curl -I the URL with the Bearer header).
  4. For 5xx, retry with backoff; check githubstatus.com.
Defensive patterns

Strategy: retry

Try / catch

import { downloadRelease, getLatestRelease } from './utils/github';

async function downloadFresh(dest: string, token?: string) {
  for (let attempt = 0; attempt < 3; attempt++) {
    const release = await getLatestRelease(token); // fresh asset URL each try
    const asset = release.assets.find(a => a.name.endsWith('.zip'));
    if (!asset) throw new Error('No zip asset on latest release');
    try {
      await downloadRelease(asset.browser_download_url, dest, token);
      return;
    } catch (e) {
      if (e instanceof GitHubDownloadError && e.message.includes('404') && attempt < 2) continue;
      throw e;
    }
  }
}

Prevention

When it happens

Trigger: Calling downloadRelease() with a browser_download_url captured from an older release JSON whose asset was since replaced (404); an invalid token producing 401 on the asset CDN redirect; expired signed S3 URL after GitHub re-uploaded the asset; transient 5xx from the asset CDN.

Common situations: Re-using a cached release object instead of re-fetching; tokens with fine-grained permissions that don't cover 'Contents: read' on the repo; asset deleted by retention policy.

Related errors


AI-assisted analysis of nextlevelbuilder/ui-ux-pro-max-skill@a38d04c3d5 (2026-08-14). Data as JSON: /api/errors/96c0e9f7060ebab6. Report an issue: GitHub.