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

  1. Retry the run — transient 5xx/429 failures often clear on a second pass
  2. For 404, find the dead image URL in the --verbose dry-run output and fix or remove that reference in the Markdown
  3. 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
  4. Use --limit to process a few URLs at a time and isolate the failing one

Example fix

# before: image gone from mdnice CDN
![arch](https://files.mdnice.com/user/1234/dead-image.png)

# after: reference a local copy already in the repo
![arch](/images/dead-image.png)
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

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.