{"record":{"id":"260bb1862335f912","repo":"Budibase/budibase","slug":"unexpected-response-body-stream-type","errorCode":null,"errorMessage":"Unexpected response body stream type","messagePattern":"Unexpected response body stream type","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/backend-core/src/objectStore/utils.ts","lineNumber":80,"sourceCode":"  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)\n\n  if (result === null) {\n    throw new Error(\"Invalid signed URL\")\n  }\n\n  const { bucket, path: objectPath } = result\n  const { stream: readStream } = await objectStore.getReadStream(","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/backend-core/src/objectStore/utils.ts#L62-L98","documentation":"processUrlAttachment requires the fetch response body to be a Node stream.Readable so it can stream content into the object store. If body exists but is not a Readable (e.g. it's a web ReadableStream/Blob from a non-Node fetch implementation), the library throws this type-mismatch error.","triggerScenarios":"The fetch implementation used by fetchWithBlacklist returns a web-standard ReadableStream instead of a Node stream.Readable — typically from a runtime mismatch, an injected fetch polyfill, or a mocked fetch in custom setups.","commonSituations":"Custom undici/nodeify patches of global fetch, running inside environments where fetch returns WHATWG streams, or tests stubbing fetch with objects whose body is not a Readable.","solutions":["Use the Node 18+ built-in fetch (undici is already nodeified) and avoid polyfills that don't return node streams","If using a custom fetch, wrap the body: stream.Readable.fromWeb(body) before returning the response","In tests, stub responses with stream.Readable instances (e.g. stream.Readable.from([buffer]))","Pin/verify the version of any global fetch overriding packages"],"exampleFix":"// before\nconst response = { ok: true, body: await res.arrayBuffer() }\n// after\nconst response = { ok: true, body: stream.Readable.fromWeb(res.body) }","handlingStrategy":"type-guard","validationCode":"const res = await fetch(url)\nif (!(res.body instanceof (await import(\"stream\")).Readable)) throw new Error(\"Body is not a node stream\")","typeGuard":"function isReadable(b: unknown): b is import(\"stream\").Readable {\n  return b instanceof (await import(\"stream\")).Readable\n}","tryCatchPattern":"try {\n  const content = await processUrlAttachment(attachment)\n} catch (err) {\n  if (err.message === \"Unexpected response body stream type\") {\n    // fetch implementation returns web streams: fix polyfill or convert\n  }\n  throw err\n}","preventionTips":["Do not replace global fetch with non-nodeifying polyfills","Convert web ReadableStreams with stream.Readable.fromWeb before passing","In tests, stub bodies with stream.Readable instances","Keep Node version (per .nvmrc) so built-in fetch returns Node streams"],"tags":["stream","type-mismatch","automation","attachments"],"backgroundTag":"stream-type-mismatch","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}