fix: preserve Request body source in patch-fetch#90886
fix: preserve Request body source in patch-fetch#90886anthony-schanen wants to merge 4 commits intovercel:canaryfrom
Conversation
|
Allow CI Workflow Run
Note: this should only be enabled once the PR is ready to go and can only be enabled by a maintainer |
unstubbable
left a comment
There was a problem hiding this comment.
Using Node v24.14.0, the test also succeeds for me without your fix.
e1448c5 to
e58d141
Compare
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 Additionally, I am not sure if using |
| reqOptions.body = ogBody | ||
| input = new Request(reqInput.url, reqOptions) | ||
| } else { | ||
| if (isStale) { |
There was a problem hiding this comment.
This is different than the original implementation. Can you add a comment explaining why this is needed.
There was a problem hiding this comment.
Added a comment explaining the branch.
timneutkens
left a comment
There was a problem hiding this comment.
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
|
Replaced the unit test with an e2e test |
|
The http server return 401
…On Wed, Mar 11, 2026, 5:11 AM Anthony Schanen ***@***.***> wrote:
*anthony-schanen* left a comment (vercel/next.js#90886)
<#90886 (comment)>
Replaced the unit test with an e2e test
—
Reply to this email directly, view it on GitHub
<#90886 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/B7H7RE64FY4VLFTHUIAS6RT4QDYPFAVCNFSM6AAAAACWHC75SCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHM2DAMZWGQ4DGNRRG4>
.
You are receiving this because you are subscribed to this thread.Message
ID: ***@***.***>
|
|
Hey @timneutkens, just wanted to bump this. Please let me know if there's anything else needed on this |
2b0d2a6 to
f3f586b
Compare
|
Is it possible for this issue to occur when the server responds with 200? I'm also seeing this issue when using Also I can't reproduce this locally even with 24.14, only in prod on Vercel. |
- 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
Head branch was pushed to by a user without write access
f3f586b to
2f9f107
Compare
|
@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 |
|
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. |
|
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 |
Fixes #90826
Related #83001
What?
We should preserve the original Request's internal body source when
patch-fetchreconstructs 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 throwsTypeError: expected non-null body source.patch-fetch.tswas extractingreqInput.body(a ReadableStream) and passing it tonew Request(reqInput.url, { body: stream }). Node 24.14+ (via undici) validates that the body has an internal "source", ReadableStreams extracted via.bodydon't carry that source.The existing
_ogBodymechanism only helps when caching is enabled (it's set duringgenerateCacheKey()). For uncached POST requests,_ogBodyis never set, so the fallback toreqInput.bodytriggers the crash.How?
Split the
isRequestInputblock into two branches:_ogBodyexists the body was consumed bygenerateCacheKey(), Request is disturbed. Reconstruct from URL string with the preserved body (thats the existing behavior)._ogBodyis absent the body stream is intact. Usenew Request(reqInput, reqOptions)instead ofnew Request(reqInput.url, reqOptions)to preserve the internal body source. This follows the same pattern asdedupe-fetch.tswhich 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.