nodejs/node · error · InvalidReturnValueError
UND_ERR_INVALID_RETURN_VALUE
UND_ERR_INVALID_RETURN_VALUE
Error message
expected Readable
What it means
Thrown by undici's PipelineHandler (code UND_ERR_INVALID_RETURN_VALUE) when the user-supplied handler returns a value that is null/undefined or lacks an 'on' method — i.e. not a Readable stream. pipeline() pumps the response body into the stream returned by the handler, so the return value MUST be a Readable (or have Readable-like .on('data'|'error'|'end')). This is a contract violation by the handler implementation, not a network error.
Source
Thrown at deps/undici/src/lib/api/api-pipeline.js:203
this.handler = null
const rawHeaders = controller?.rawHeaders
const responseHeaders = this.responseHeaders === 'raw'
? util.parseRawHeaders(rawHeaders)
: headers
body = this.runInAsyncScope(handler, null, {
statusCode,
headers: responseHeaders,
opaque,
body: this.res,
context
})
} catch (err) {
this.res.on('error', noop)
throw err
}
if (!body || typeof body.on !== 'function') {
throw new InvalidReturnValueError('expected Readable')
}
body
.on('data', (chunk) => {
const { ret, body } = this
if (!ret.push(chunk) && body.pause) {
body.pause()
}
})
.on('error', (err) => {
const { ret } = this
util.destroy(ret, err)
})
.on('end', () => {
const { ret } = this
View on GitHub (pinned to 1b2de5e052)
Solutions
- Ensure the handler returns a Readable stream — typically create one with new PassThrough()/Readable.from() and return it.
- Do not return a Promise; do the work synchronously and return the stream, or pipe inside the handler.
- If transforming, read from the provided res.body and push into a Readable you create and return.
- Verify the returned object has .on('data')/.on('error')/.on('end').
Example fix
// before
undici.pipeline(url, {}, ({ body }) => {
body.on('data', () => {}) // returns undefined -> 'expected Readable'
})
// after
const { PassThrough } = require('node:stream')
undici.pipeline(url, {}, ({ body }) => {
const pass = new PassThrough()
body.pipe(pass)
return pass
}) Defensive patterns
Strategy: type-guard
Validate before calling
function ensureReadable(ret) {
if (!ret || typeof ret.on !== 'function') throw new TypeError('pipeline handler must return a Readable stream')
return ret
} Type guard
function isReadableLike(v: unknown): v is NodeJS.ReadableStream {
return !!v && typeof (v as any).on === 'function'
} Try / catch
// pipeline throws synchronously inside the handler scope; wrap handler body:
undici.pipeline(url, opts, (res) => {
try {
const ret = transform(res)
if (!ret || typeof ret.on !== 'function') throw new TypeError('handler must return a Readable')
return ret
} catch (err) { console.error(err); throw err }
}) Prevention
- Always return a Readable (e.g. new PassThrough()) from the handler.
- Do not return a Promise — do work synchronously or pipe inside.
- Pipe the incoming res.body into the returned stream.
- Unit-test the handler in isolation to assert it returns a value with .on.
When it happens
Trigger: pipeline() handler that returns undefined (forgot return), a string, a Promise, an object literal, or a Writable-only stream without .on. Any of these causes onResponseStart to throw 'expected Readable'.
Common situations: Handler returns nothing after `await`; handler returns the response object itself instead of a transformed stream; handler returns a Promise of a stream (pipeline does not await); passing a sink/Writable where a Readable is required.
Related errors
- UND_ERR_INVALID_ARG
- UND_ERR_INVALID_ARG
- UND_ERR_INVALID_RETURN_VALUE
- UND_ERR_INVALID_ARG
- UND_ERR_INVALID_ARG
AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13).
Data as JSON: /api/errors/bc49ff6d3dcaa19a.
Report an issue: GitHub.