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
- Re-export the space from Contentful so sourceUrl values are current, then rerun the import (already-downloaded assets are skipped by the size check)
- 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
- 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
- Run the import soon after taking the Contentful export so asset URLs are fresh
- Rerun on failure — the size-equality check makes downloads idempotent
- Delete stale assets from the export if they no longer exist in the space
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
- Usage: node site/scripts/import-contentful-export.mjs <previ
- HTTP ${res.status} - ${res.statusText}
- Asset ${asset.id} size mismatch: expected ${asset.size}, rec
- The Contentful export is missing the landing or learn entry
- Blog entry ${item.sys.id} has no slug
AI-assisted analysis of unionlabs/union@031785bb6d (2026-08-16).
Data as JSON: /api/errors/d2964112e9f54fcf.
Report an issue: GitHub.