Skip to content

fix: preserve Request body source in patch-fetch#90886

Open
anthony-schanen wants to merge 4 commits intovercel:canaryfrom
anthony-schanen:fix/patch-fetch-request-body-source
Open

fix: preserve Request body source in patch-fetch#90886
anthony-schanen wants to merge 4 commits intovercel:canaryfrom
anthony-schanen:fix/patch-fetch-request-body-source

Conversation

@anthony-schanen
Copy link
Copy Markdown

@anthony-schanen anthony-schanen commented Mar 4, 2026

Fixes #90826
Related #83001

What?

We should preserve the original Request's internal body source when patch-fetch reconstructs the Request for uncached POST requests.

Why?

When fetch(new Request(url, { method: 'POST', body: JSON.stringify({}) })) is called in a Server Action or Route Handler on Node >= 24.14.0, it throws TypeError: expected non-null body source.

patch-fetch.ts was extracting reqInput.body (a ReadableStream) and passing it to new Request(reqInput.url, { body: stream }). Node 24.14+ (via undici) validates that the body has an internal "source", ReadableStreams extracted via .body don't carry that source.

The existing _ogBody mechanism only helps when caching is enabled (it's set during generateCacheKey()). For uncached POST requests, _ogBody is never set, so the fallback to reqInput.body triggers the crash.

How?

Split the isRequestInput block into two branches:

  • if _ogBody exists the body was consumed by generateCacheKey(), Request is disturbed. Reconstruct from URL string with the preserved body (thats the existing behavior).
  • if _ogBody is absent the body stream is intact. Use new Request(reqInput, reqOptions) instead of new Request(reqInput.url, reqOptions) to preserve the internal body source. This follows the same pattern as dedupe-fetch.ts which already reuses the original Request to avoid disturbing the body.

I also added a unit test verifying uncached POST requests preserve method and non-null body through to originFetch. Verified with reproduction repo on Node 24.14.0.

@nextjs-bot
Copy link
Copy Markdown
Contributor

nextjs-bot commented Mar 4, 2026

Allow CI Workflow Run

  • approve CI run for commit: 512af5cc7cf38671ae3cb73bb15c53a253529f53

Note: this should only be enabled once the PR is ready to go and can only be enabled by a maintainer

Copy link
Copy Markdown
Contributor

@unstubbable unstubbable left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using Node v24.14.0, the test also succeeds for me without your fix.

@anthony-schanen anthony-schanen force-pushed the fix/patch-fetch-request-body-source branch from e1448c5 to e58d141 Compare March 7, 2026 01:33
@anthony-schanen
Copy link
Copy Markdown
Author

Using Node v24.14.0, the test also succeeds for me without your fix.

Thanks for pointing that mistake out. The mocked fetch never went through undici, so the body source validation did not get triggered. I've fixed the test. It now spins up a HTTP server and returns 401 (another mistake in the test). That should match the reproduction from the original issue's reproduction repo. I have the test failing without the fix implemented showing
expected non-null body source and passing with the fix implemented.

Additionally, I am not sure if using createServer in a unit test is okay here. I didn't see it used in any other instance within related unit tests, but I could not ideate a way to trigger the undici validation with a mock.

reqOptions.body = ogBody
input = new Request(reqInput.url, reqOptions)
} else {
if (isStale) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is different than the original implementation. Can you add a comment explaining why this is needed.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment explaining the branch.

@vercel vercel deleted a comment from mondyzzz-glitch Mar 10, 2026
Copy link
Copy Markdown
Contributor

@timneutkens timneutkens left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth adding an e2e test for this instead of a unit test to make sure it's fully covered.

The contributing docs explain how to set it up using pnpm new-test

@anthony-schanen
Copy link
Copy Markdown
Author

Replaced the unit test with an e2e test

@spencerwalsh651-cmyk
Copy link
Copy Markdown

spencerwalsh651-cmyk commented Mar 11, 2026 via email

@anthony-schanen
Copy link
Copy Markdown
Author

Hey @timneutkens, just wanted to bump this. Please let me know if there's anything else needed on this

@unstubbable unstubbable force-pushed the fix/patch-fetch-request-body-source branch from 2b0d2a6 to f3f586b Compare April 22, 2026 09:35
@tom-sherman
Copy link
Copy Markdown

Is it possible for this issue to occur when the server responds with 200? I'm also seeing this issue when using new Request but in my case I'm pretty sure the server is responding with an ok.

Also I can't reproduce this locally even with 24.14, only in prod on Vercel.

anthony-schanen and others added 4 commits April 23, 2026 07:52
- Remove unit test for Request body source preservation
- Add e2e test with external HTTP server to verify the fix
  through the full Next.js stack on Node 24.14+
- Add code comment explaining why the original Request object
  is passed instead of reqInput.url
auto-merge was automatically disabled April 23, 2026 14:54

Head branch was pushed to by a user without write access

@anthony-schanen anthony-schanen force-pushed the fix/patch-fetch-request-body-source branch from f3f586b to 2f9f107 Compare April 23, 2026 14:54
@anthony-schanen
Copy link
Copy Markdown
Author

@unstubbable My commits were not originally signed. I just attempted to sign them now but auto-merge disabled as a result. Just a heads up

@tom-sherman
Copy link
Copy Markdown

I found the corresponding undici bug: nodejs/undici#5004 (comment)

Also, I was able to reproduce this on servers that respond with 407 - not just 401. These were the only two status codes I could find that triggered the bug.

@anthony-schanen
Copy link
Copy Markdown
Author

I found the corresponding undici bug: nodejs/undici#5004 (comment)

Also, I was able to reproduce this on servers that respond with 407 - not just 401. These were the only two status codes I could find that triggered the bug.

I suppose this should be triggered by anything that would cause the source to be consulted when it's null. This probably happens on 4xx errors. As for your 200 scenario I'm not sure. Just speculating though.

@tom-sherman
Copy link
Copy Markdown

For my case turns out the server was actually returning 401, I didn't have the correct logging setup to catch this 😅 looks like there's a specific regression in undici for this so this fix only needs to exist while the fix to that regression rolls out. Probably worth adding the fix PR as a code comment? nodejs/undici#4941

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: expected non-null body source when passing a Request object to fetch AND receiving an error response (Node >= 24.14.0)

6 participants