itwanger/toBeBetterJavaer · error · Error
Download failed with HTTP ${response.status}
Error message
Download failed with HTTP ${response.status} What it means
Thrown by downloadImage() in scripts/convert-mdnice-images-to-cdn.js when an HTTP fetch of an mdnice image URL returns a non-2xx status (response.ok is false). Fetch follows redirects and sends a custom User-Agent (toBeBetterJavaer-image-cdn/1.0). The thrown message embeds the exact HTTP status so you can distinguish 404 dead links from 403 anti-hotlink blocks from 5xx origin failures.
Source
Thrown at scripts/convert-mdnice-images-to-cdn.js:365
const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
while (cursor < items.length) {
const index = cursor;
cursor += 1;
await worker(items[index], index);
}
});
await Promise.all(workers);
}
async function downloadImage(url) {
const response = await fetch(url, {
redirect: "follow",
headers: {
"User-Agent": "toBeBetterJavaer-image-cdn/1.0",
},
});
if (!response.ok) {
throw new Error(`Download failed with HTTP ${response.status}`);
}
const contentType = normalizeContentType(response.headers.get("content-type"));
if (contentType && !contentType.startsWith("image/") && contentType !== "application/octet-stream") {
throw new Error(`Unexpected content type: ${contentType}`);
}
const body = Buffer.from(await response.arrayBuffer());
if (body.length === 0) {
throw new Error("Downloaded file is empty");
}
return { body, contentType };
}
function normalizeContentType(contentType) {
if (!contentType) {
return "";
}
return contentType.split(";")[0].trim().toLowerCase();
}View on GitHub (pinned to 6617f5fd0b)
Solutions
- Retry the run — transient 5xx/429 failures often clear on a second pass
- For 404, find the dead image URL in the --verbose dry-run output and fix or remove that reference in the Markdown
- For 403, test the URL with the same User-Agent (`curl -A toBeBetterJavaer-image-cdn/1.0 <url>`); if blocked, download the image manually, put it in docs, and reference it locally
- Use --limit to process a few URLs at a time and isolate the failing one
Example fix
# before: image gone from mdnice CDN  # after: reference a local copy already in the repo 
Defensive patterns
Strategy: retry
Validate before calling
// Cheap pre-check for a specific URL before a --write run
const res = await fetch(url, { method: "HEAD", headers: { "User-Agent": "toBeBetterJavaer-image-cdn/1.0" } });
if (!res.ok) console.warn(`Will fail: ${url} -> HTTP ${res.status}`); Try / catch
catch (err) { if (/^Download failed with HTTP (4\d\d|5\d\d)$/.test(err.message)) { if (isTransient(err.message)) retryWithBackoff(); else logDeadLink(err.message); } else throw err; } Prevention
- Run --verbose dry runs periodically to spot dead mdnice links early
- Retry once on 5xx/429 before treating a URL as dead
- Mirror important images into the repo so the script is not the only copy path
When it happens
Trigger: A Markdown file references a files.mdnice.com image that was deleted (404); the CDN blocks the custom User-Agent or referer-less requests (403); rate limiting (429); transient origin errors (5xx). Only fires during a --write run since dry runs only scan.
Common situations: Old articles whose mdnice-hosted images expired; hotlink protection on the source CDN; flaky mobile-network or proxy environments; a proxy/interceptor returning 407/502; mdnice changing URL signing.
Related errors
AI-assisted analysis of itwanger/toBeBetterJavaer@6617f5fd0b (2026-08-14).
Data as JSON: /api/errors/89194e4f1fb32dd2.
Report an issue: GitHub.