nodejs/node · error · InvalidReturnValueError

UND_ERR_INVALID_RETURN_VALUE

UND_ERR_INVALID_RETURN_VALUE

Error message

expected Writable

What it means

Thrown by undici's StreamHandler (InvalidReturnValueError, code UND_ERR_INVALID_RETURN_VALUE) at response time when the factory function returns a value that is missing .write, .end, or .on. The factory must return a Node.js Writable stream so the response body can be pumped into it. Returning null, a string, a Promise, or a non-stream object triggers this.

Source

Thrown at deps/undici/src/lib/api/api-stream.js:164

    if (factory === null) {
      return
    }

    const res = this.runInAsyncScope(factory, null, {
      statusCode,
      headers: responseHeaderData,
      opaque,
      context
    })

    if (
      !res ||
      typeof res.write !== 'function' ||
      typeof res.end !== 'function' ||
      typeof res.on !== 'function'
    ) {
      throw new InvalidReturnValueError('expected Writable')
    }

    trackWritableLifecycle(res, (err, fromErrorEvent) => {
      const { callback, res, opaque, trailers, abort } = this

      this.res = null
      if (err || !res?.readable) {
        util.destroy(res, fromErrorEvent ? undefined : err)
      }

      this.callback = null
      this.runInAsyncScope(callback, null, err || null, { opaque, trailers })

      if (err) {
        abort(err)
      }
    })

View on GitHub (pinned to 1b2de5e052)

Solutions

  1. Ensure the factory is synchronous and returns a Node Writable with write/end/on (e.g. fs.createWriteStream, stream.Writable, http.ServerResponse).
  2. If the factory must be async, create the Writable synchronously inside it and resolve setup asynchronously rather than returning a Promise.
  3. Confirm the returned object is not a WHATWG WritableStream — convert via stream.Writable.fromWeb() if needed.

Example fix

// before
stream(opts, async ({ statusCode }) => fs.createWriteStream(path), cb) // async -> Promise

// after
stream(opts, ({ statusCode }) => fs.createWriteStream(path), cb)
Defensive patterns

Strategy: try-catch

Validate before calling

function wrapFactory(f) {
  return (ctx) => {
    const res = f(ctx)
    if (!res || typeof res.write !== 'function' || typeof res.end !== 'function' || typeof res.on !== 'function') {
      throw new Error('factory must return a Node Writable')
    }
    return res
  }
}
// stream(opts, wrapFactory(myFactory), cb)

Type guard

function isNodeWritable(v) {
  return !!v && typeof v.write === 'function' && typeof v.end === 'function' && typeof v.on === 'function'
}

Try / catch

try {
  stream(opts, factory, cb)
} catch (e) {
  if (e.code === 'UND_ERR_INVALID_RETURN_VALUE') {
    // factory returned a non-Writable; fix it to return a Node Writable and retry
  } else throw e
}

Prevention

When it happens

Trigger: Factory returns undefined (e.g. forgot return), returns a Promise<Writable> (async factory), returns an object literal, or returns a web ReadableStream/WritableStream (WHATWG) instead of a Node stream.

Common situations: Async factory functions whose returned stream is awaited implicitly; returning fs.createWriteStream conditionally such that some branches return nothing; mixing up Web Streams API with Node streams.

Related errors


AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13). Data as JSON: /api/errors/bf29acf548b6b9d0. Report an issue: GitHub.