Skip to content

Request with ReadableStream body causes 'expected non-null body source' assertion failure in undici 7.x #5004

@satyam-mishra-pce

Description

@satyam-mishra-pce

Environment

  • Node.js: v24.14.0 (undici 7.21.0)
  • Platform: macOS 26.4 (Apple Silicon)

Description

In undici 7.x, creating a Request with a ReadableStream body causes an assertion failure:

Error: expected non-null body source

This is a breaking change from undici 6.x (Node 22 and earlier) where this worked fine.

Reproduction

const body = new TextEncoder().encode(JSON.stringify({ test: true }));

// Create original Request with Uint8Array body
const originalReq = new Request('https://example.com', {
  method: 'POST',
  body,
  headers: { 'Content-Type': 'application/json' }
});

// Read the body as ReadableStream
const bodyStream = originalReq.body;

// Create new Request from ReadableStream (common pattern in middleware/proxies)
const newReq = new Request(originalReq.url, {
  method: originalReq.method,
  headers: originalReq.headers,
  body: bodyStream,
  duplex: 'half',
});

// Try to fetch with the new request
await fetch(newReq);
// → Error: expected non-null body source

Root Cause

In undici/lib/web/fetch/index.js, there's an assertion:

if (request.body != null) {
  assert(request.body.source != null)
  request.body = safelyExtractBody(request.body.source)[0]
}

When a Request is created with a ReadableStream body (instead of Uint8Array, string, etc.), the body.source property is null, causing the assertion to fail.

Impact

This breaks legitimate use cases:

  • Middleware that re-creates requests (e.g., Next.js's fetch patching, proxies)
  • Libraries that create intermediate Request objects (e.g., @atproto-labs/fetch-node for SSRF protection)

Expected Behavior

Creating a Request with a ReadableStream body should work, as it did in undici 6.x (Node 22).

Workaround

Downgrade to Node.js 22 (undici 6.x) or pre-read the body as ArrayBuffer before creating the new Request.

Question

Is this strict assertion intentional? If so, what's the recommended pattern for middleware that needs to re-create Request objects?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions