unionlabs/union · error · Error

Failed to download asset ${asset.id}: ${response.status} ${r

Error message

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

What it means

Thrown while the Contentful import script downloads asset binaries: for each asset whose local file size differs from the export's recorded size, it fetches asset.sourceUrl; a non-ok HTTP response aborts the import with the asset id plus status code and reason.

Source

Thrown at site/scripts/import-contentful-export.mjs:171

const downloadableAssets = assetManifest.filter(
  (asset) => asset.sourceUrl && asset.path,
)

await mapConcurrent(downloadableAssets, 8, async (asset) => {
  const target = join(SITE_DIRECTORY, "public", asset.path)
  await mkdir(join(target, ".."), { recursive: true })

  let existingSize = null
  try {
    existingSize = (await stat(target)).size
  } catch {
    // The file has not been downloaded yet.
  }

  if (existingSize !== asset.size) {
    const response = await fetch(asset.sourceUrl)
    if (!response.ok) {
      throw new Error(
        `Failed to download asset ${asset.id}: ${response.status} ${response.statusText}`,
      )
    }
    const bytes = new Uint8Array(await response.arrayBuffer())
    if (asset.size !== null && bytes.byteLength !== asset.size) {
      throw new Error(
        `Asset ${asset.id} size mismatch: expected ${asset.size}, received ${bytes.byteLength}`,
      )
    }
    await writeFile(target, bytes)
  }

  completedAssets += 1
  if (
    completedAssets % 25 === 0
    || completedAssets === downloadableAssets.length
  ) {
    console.log(`assets: ${completedAssets}/${downloadableAssets.length}`)

View on GitHub (pinned to 031785bb6d)

Solutions

  1. Re-export the space from Contentful so sourceUrl values are current, then rerun the import (already-downloaded assets are skipped by the size check)
  2. Check the specific asset id in the message in the Contentful web app; if it was deleted, remove it from the export or re-publish
  3. Verify network access to assets.contentful.com (proxy/firewall) and retry — the size-mismatch skip makes reruns cheap
Defensive patterns

Strategy: retry

Validate before calling

import { stat } from "node:fs/promises"

const needsDownload = async (target: string, expectedSize: number | null) => {
  try {
    return (await stat(target)).size !== expectedSize
  } catch {
    return true
  }
}
// The script already skips matching files; pre-check connectivity if you must:
await fetch(asset.sourceUrl, { method: "HEAD" })

Try / catch

for (const asset of assets) {
  try {
    await downloadAsset(asset)
  } catch (error) {
    if ((error as Error).message.startsWith(`Failed to download asset ${asset.id}`)) {
      // log and continue; rerun later — completed assets are skipped via the size check
      continue
    }
    throw error
  }
}

Prevention

When it happens

Trigger: Contentful CDN returning 404 (asset deleted or URL changed since the export), 401/403 (space made private, signed URLs expired), or 5xx (CDM outage) when GET-ing assets.contentful.com/<space>/<asset>/<name>.

Common situations: Old export archives whose asset URLs no longer resolve; spaces switched from public to private; network/proxy blocking the CDN; running the import long after the export was taken.

Related errors


AI-assisted analysis of unionlabs/union@031785bb6d (2026-08-16). Data as JSON: /api/errors/d2964112e9f54fcf. Report an issue: GitHub.