{"record":{"id":"64e31f232c62dcd3","repo":"Budibase/budibase","slug":"unexpected-response-response-statustext-64e31f","errorCode":null,"errorMessage":"Unexpected response ${response.statusText}","messagePattern":"Unexpected response (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/backend-core/src/objectStore/utils.ts","lineNumber":73,"sourceCode":"      Days: days,\n    },\n  }\n  const lifecycleConfiguration = {\n    Rules: [lifecycleRule],\n  }\n\n  return {\n    Bucket: bucketName,\n    LifecycleConfiguration: lifecycleConfiguration,\n  }\n}\n\nasync function processUrlAttachment(\n  attachment: AutomationAttachment\n): Promise<AutomationAttachmentContent> {\n  const response = await fetchWithBlacklist(attachment.url)\n  if (!response.ok || !response.body) {\n    throw new Error(`Unexpected response ${response.statusText}`)\n  }\n  const fallbackFilename = path.basename(new URL(attachment.url).pathname)\n  if (!response.body) {\n    throw new Error(\"No response received for attachment\")\n  }\n  if (!(response.body instanceof stream.Readable)) {\n    throw new Error(\"Unexpected response body stream type\")\n  }\n  return {\n    filename: attachment.filename || fallbackFilename,\n    content: response.body,\n  }\n}\n\nexport async function processObjectStoreAttachment(\n  attachment: AutomationAttachment\n): Promise<BucketedContent> {\n  const result = objectStore.extractBucketAndPath(attachment.url)","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/backend-core/src/objectStore/utils.ts#L55-L91","documentation":"processUrlAttachment downloads a remote attachment for an automation via fetchWithBlacklist and requires both an OK (2xx) status and a non-null body. If either fails, it throws 'Unexpected response <statusText>'. This is the automation attachment ingestion path.","triggerScenarios":"An automation attachment step whose attachment.url returns 404/403/500 or redirects to an error page, or a response whose body is missing (e.g. 204 or a HEAD-like response).","commonSituations":"Automations referencing signed URLs that expired, links to files moved/deleted from external servers, or target sites blocking server-side fetches (bot protection returning 403).","solutions":["Verify the attachment URL works with curl -I from the server; fix or regenerate the URL if it returns 404/410","Handle expired signed URLs — re-generate the link or re-upload the file before the automation runs","403: the remote may block non-browser clients; host the file somewhere fetchable or add a proxy with proper headers","Check fetchWithBlacklist isn't rejecting the host via configured blacklist"],"exampleFix":"// before\nattachment.url = \"https://example.com/file.pdf\" // possibly expired signed URL\n// after\n// ensure URL is fresh/valid before building the automation attachment\nconst res = await fetch(url, { method: \"HEAD\" })\nif (res.ok) attachment.url = url","handlingStrategy":"validation","validationCode":"const res = await fetch(url, { method: \"HEAD\" })\nif (!res.ok) throw new Error(`Attachment URL invalid: ${res.status}`)","typeGuard":null,"tryCatchPattern":"try {\n  const content = await processAutomationAttachment(attachment)\n} catch (err) {\n  if (err.message.startsWith(\"Unexpected response\")) {\n    // regenerate or replace the attachment URL\n  }\n  throw err\n}","preventionTips":["Validate attachment URLs return 2xx with HEAD before scheduling automations","Avoid expired signed URLs — regenerate or use permanent links","Host files on endpoints that allow server-side fetches","Confirm hosts are not blacklisted for server egress"],"tags":["http","network","automation","attachments"],"backgroundTag":"http-non-ok-response","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}