{"record":{"id":"3589989349cb13ae","repo":"Budibase/budibase","slug":"failed-to-fetch-file-from-url-fallbackresponse","errorCode":null,"errorMessage":"Failed to fetch file from URL: ${fallbackResponse.statusText}","messagePattern":"Failed to fetch file from URL: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/server/src/automations/steps/ai/extract.ts","lineNumber":169,"sourceCode":"    return { kind: \"image\", value: toImageDataUrl(data, fileType) }\n  }\n\n  const filename = `document.${fileType || \"pdf\"}`\n  try {\n    const uploaded = await llm.uploadFile(\n      response.body as Readable,\n      filename,\n      fileType\n    )\n    return {\n      kind: \"file\",\n      value: uploaded,\n    }\n  } catch (error) {\n    if (shouldInlineFileAfterUploadFailure(error)) {\n      const fallbackResponse = await fetchWithBlacklist(fileUrl)\n      if (!fallbackResponse.ok) {\n        throw new Error(\n          `Failed to fetch file from URL: ${fallbackResponse.statusText}`\n        )\n      }\n      const data = await fallbackResponse.buffer()\n      const text = await extractPdfText(data)\n      return { kind: \"text\", value: text }\n    }\n    throw error\n  }\n}\n\nasync function processAttachmentFile(\n  attachment: any,\n  llm: LLMResponse\n): Promise<ExtractInput> {\n  const bucket = objectStore.ObjectStoreBuckets.APPS\n  const { stream } = await objectStore.getReadStream(bucket, attachment.key!)\n  const contentType = attachment.extension","sourceCodeStart":151,"sourceCodeEnd":187,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/automations/steps/ai/extract.ts#L151-L187","documentation":"After an attempt to pass the file to the LLM via upload fails, processUrlFile falls back to inlining: it re-fetches the file URL itself and, if that fallback fetch is not OK, throws with the fallback response's statusText. This is the same download failure as the primary path but reached via the upload-failure fallback branch.","triggerScenarios":"shouldInlineFileAfterUploadFailure(error) is true (LLM upload failed) and the subsequent fetchWithBlacklist(fileUrl) returns non-OK (404/403/5xx), so the inline-text fallback cannot download the file either.","commonSituations":"LLM upload fails AND the source URL has expired or returns errors; transient upstream outages hit both the upload and the direct fetch; rate-limited or IP-blocked asset host.","solutions":["Confirm the file URL returns 200 with curl and replace it with a working URL","Re-upload the file to accessible storage and update the step's fileUrl","If the upload failure is persistent, check LLM provider status/quota so the fallback isn't needed","Retry the automation if the upstream outage was transient"],"exampleFix":"// before\nfileUrl: \"https://example.com/tmp/report.pdf\" // signed URL expired → 403\n// after\nfileUrl: \"https://example.com/permanent/report.pdf\" // 200","handlingStrategy":"fallback","validationCode":"const check = await fetchWithBlacklist(fileUrl)\nif (!check.ok) throw new Error(`Source URL dead before run: ${check.status}`)","typeGuard":null,"tryCatchPattern":"try {\n  const input = await processUrlFile(params, fileUrl, fileType, llm)\n} catch (err) {\n  if (err.message.startsWith(\"Failed to fetch file from URL:\")) {\n    // both upload and inline fallback failed; surface URL as unusable\n  }\n  throw err\n}","preventionTips":["Ensure the URL works independently of the LLM upload path","Use permanent hosting for files used in automations","Monitor LLM provider availability so the fallback path isn't hit unexpectedly"],"tags":["http","fetch","fallback","ai","file-download"],"backgroundTag":"http-request-failed","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}