Budibase/budibase · error · Error

Failed to download asset: ${response.statusText}

Error message

Failed to download asset: ${response.statusText}

What it means

downloadAssetsForExtract fetches model-unsupported URLs manually (when the LLM cannot download the asset itself) using plain fetch. Any non-OK HTTP status causes it to throw with the response's statusText, aborting the extract step's asset preparation.

Source

Thrown at packages/server/src/automations/steps/ai/extract.ts:91

    "The data array must contain at most 1 object.",
    "Do not include markdown, explanations, or extra keys.",
    'If no matching data is found, return {"data": []}.',
  ].join("\n\n")
}

const downloadAssetsForExtract: Experimental_DownloadFunction =
  async requests =>
    Promise.all(
      requests.map(async ({ url, isUrlSupportedByModel }) => {
        if (url.protocol === "data:") {
          return null
        }
        if (isUrlSupportedByModel) {
          return null
        }
        const response = await fetch(url)
        if (!response.ok) {
          throw new Error(`Failed to download asset: ${response.statusText}`)
        }
        return {
          data: new Uint8Array(await response.arrayBuffer()),
          mediaType: response.headers.get("content-type") ?? undefined,
        }
      })
    )

function buildExtractModelMessages(input: ExtractInput): ModelMessage[] {
  const prompt = buildExtractPrompt()
  const userContent: UserContent =
    input.kind === "image"
      ? [
          {
            type: "image",
            image: new URL(input.value),
          },
          {

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Verify the asset URL is publicly reachable (curl it) and returns 200
  2. Re-upload or regenerate the asset / signed URL if it expired or was deleted
  3. Ensure the URL requires no auth headers that plain fetch does not send
  4. Check the asset server/CDN logs for the failing status

Example fix

// before
const response = await fetch(expiredSignedUrl) // 403 Forbidden
// after
const freshUrl = await regenerateSignedUrl(assetId)
const response = await fetch(freshUrl) // 200 OK
Defensive patterns

Strategy: retry

Validate before calling

const head = await fetch(url, { method: "HEAD" })
if (!head.ok) throw new Error(`Asset URL unreachable: ${head.status}`)

Try / catch

try {
  await downloadAssetsForExtract(url, isUrlSupportedByModel)
} catch (err) {
  if (err.message.startsWith("Failed to download asset:")) {
    // retry with backoff or refresh the asset URL
  }
  throw err
}

Prevention

When it happens

Trigger: fetch(url) in downloadAssetsForExtract returns response.ok === false — e.g. 404 (asset moved/deleted), 403 (private/permission-denied URL), 500 from the asset server — for a URL the model cannot ingest directly.

Common situations: Attachment/upload URLs that expired (signed URLs past expiry); assets behind authentication; public URLs that later 404; CDN or storage (S3/MinIO) misconfiguration returning errors.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29). Data as JSON: /api/errors/4eb17e799507ece5. Report an issue: GitHub.