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
- Re-fetch the release (getLatestRelease) immediately before downloading so the asset URL is fresh, then retry.
- Confirm the asset still exists on the release page; if re-uploaded, invalidate any cached release JSON.
- 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).
- 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
- Never cache asset URLs for long — re-fetch the release JSON right before download.
- Verify the token scope covers Contents:read when assets live on a private repo.
- Check downloaded size and magic bytes after download to catch corrupted bodies.
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
- Failed to fetch releases: ${response.status} ${response.stat
- Failed to fetch latest release: ${response.status} ${respons
- GitHub API rate limit exceeded. Resets at ${resetDate}.\n${g
- No ZIP asset found in latest release
AI-assisted analysis of nextlevelbuilder/ui-ux-pro-max-skill@a38d04c3d5 (2026-08-14).
Data as JSON: /api/errors/96c0e9f7060ebab6.
Report an issue: GitHub.