nextjs - 💡(How to fix) Fix Race condition in Node.js middleware body cloning still causing production errors despite PR #85418 [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
vercel/next.js#86373Fetched 2026-04-08 02:11:22
View on GitHub
Comments
1
Participants
2
Timeline
6
Reactions
0
Author
Timeline (top)
subscribed ×2closed ×1commented ×1labeled ×1

Error Message

⨯ TypeError: Response body object should not be disturbed or locked at new NextRequest (.next/server/chunks/10404.js:4220:14) at NextRequestAdapter.fromNodeNextRequest (.next/server/chunks/10404.js:3770:16) at handler (.next/server/app/(backend)/trpc/lambda/[trpc]/route.js:618:127)

Root Cause

After investigating Next.js source code and related issues (#83453, #85416), we identified that the problem occurs in Next.js's internal body cloning mechanism:

The race condition happens here:

// packages/next/src/server/next-server.ts (Line ~1757)
} finally {
  if (hasRequestBody) {
    requestData.body.finalize();  // ❌ Missing await!
  }
}

The finalize() method is async but not awaited:

// packages/next/src/server/body-streams.ts
export interface CloneableBody {
  finalize(): Promise<void>  // Returns a Promise
  cloneBodyStream(): Readable
}

Timeline of the race condition:

T0: Request arrives, body cloning starts
    └─ cloneBodyStream() creates PassThrough streams

T1: Middleware processes with cloned stream

T2: Middleware completes
    └─ finalize() called WITHOUT await ❌

T3: Route handler immediately tries to read req.body
    └─ Original stream may still be transferring data

T4: finalize() still executing in background
    └─ replaceRequestBody() not yet completed

T5: undici detects disturbed stream state
    └─ Throws "Response body object should not be disturbed or locked"

Fix Action

Temporary Workaround

For others affected by this issue, the only reliable workaround is to patch Next.js:

Create .patches/[email protected]:

diff --git a/dist/server/next-server.js b/dist/server/next-server.js
--- a/dist/server/next-server.js
+++ b/dist/server/next-server.js
@@ -1241,7 +1241,7 @@ class NextNodeServer extends _baseserver.default {
                 });
             } finally{
                 if (hasRequestBody) {
-                    requestData.body.finalize();
+                    await requestData.body.finalize();
                 }
             }
         } else {

Reference in package.json:

{
  "pnpm": {
    "patchedDependencies": {
      "[email protected]": "patches/[email protected]"
    }
  }
}

Note: This issue affects any production application using:

  • Node.js runtime middleware (not Edge runtime)
  • Request body reading in route handlers
  • Frameworks like tRPC, Server Actions with file uploads, or custom API routes
  • Moderate to large request payloads

The fix is already merged but not yet released, causing production issues for many users who have upgraded to Next.js 16.

Code Example

TypeError: Response body object should not be disturbed or locked
    at new NextRequest (.next/server/chunks/10404.js:4220:14)
    at NextRequestAdapter.fromNodeNextRequest (.next/server/chunks/10404.js:3770:16)
    at handler (.next/server/app/(backend)/trpc/lambda/[trpc]/route.js:618:127)

---

TypeError: Response body object should not be disturbed or locked
    at new NextRequest (.next/server/chunks/10404.js:4220:14)
    at NextRequestAdapter.fromNodeNextRequest (.next/server/chunks/10404.js:3770:16)
    at handler (.next/server/app/(backend)/webapi/chat/[provider]/route.js:569:53)

---

Operating System:
  Platform: linux (Docker)
  Version: Docker image lobehub/lobe-chat:2.0.0-next.89
Binaries:
  Node: v20.x
  pnpm: 9.x
Relevant Packages:
  next: 16.0.1
  react: 19.0.0
  react-dom: 19.0.0
Next.js Config:
  output: standalone
  runtime: nodejs (middleware)

---

// Our attempted fix in auth middleware
export const checkAuth = (handler: RequestHandler) => async (req: Request, options: RequestOptions) => {
  const clonedReq = req.clone();  // This doesn't solve the issue
  // ...
  return handler(clonedReq, { ...options, jwtPayload });
};

// Our attempted fix in tRPC handlers
const handler = (req: NextRequest) => {
  const preparedReq = prepareRequestForTRPC(req);  // Using req.clone()
  return fetchRequestHandler({
    req: preparedReq,
    // ...
  });
};

---

// packages/next/src/server/next-server.ts (Line ~1757)
} finally {
  if (hasRequestBody) {
    requestData.body.finalize();  // ❌ Missing await!
  }
}

---

// packages/next/src/server/body-streams.ts
export interface CloneableBody {
  finalize(): Promise<void>  // Returns a Promise
  cloneBodyStream(): Readable
}

---

T0: Request arrives, body cloning starts
    └─ cloneBodyStream() creates PassThrough streams

T1: Middleware processes with cloned stream

T2: Middleware completes
    └─ finalize() called WITHOUT await
T3: Route handler immediately tries to read req.body
    └─ Original stream may still be transferring data

T4: finalize() still executing in background
    └─ replaceRequestBody() not yet completed

T5: undici detects disturbed stream state
    └─ Throws "Response body object should not be disturbed or locked"

---

} finally {
  if (hasRequestBody) {
-   requestData.body.finalize();
+   await requestData.body.finalize();
  }
}

---

diff --git a/dist/server/next-server.js b/dist/server/next-server.js
--- a/dist/server/next-server.js
+++ b/dist/server/next-server.js
@@ -1241,7 +1241,7 @@ class NextNodeServer extends _baseserver.default {
                 });
             } finally{
                 if (hasRequestBody) {
-                    requestData.body.finalize();
+                    await requestData.body.finalize();
                 }
             }
         } else {

---

{
  "pnpm": {
    "patchedDependencies": {
      "[email protected]": "patches/[email protected]"
    }
  }
}
RAW_BUFFERClick to expand / collapse

Race condition in Node.js middleware body cloning still causing production errors despite PR #85418

Link to the code that reproduces this issue

https://github.com/lobehub/lobe-chat/issues/10309

To Reproduce

  1. Deploy a Next.js 16 application with Node.js runtime middleware
  2. Use tRPC or similar frameworks that read request body in route handlers
  3. Send requests with moderate-sized payloads (>50KB) or trigger multiple tool calls
  4. The error occurs intermittently, especially under load or with concurrent requests

Current vs. Expected behavior

Current behavior:

The application throws TypeError: Response body object should not be disturbed or locked intermittently in production, even after implementing the commonly suggested req.clone() workaround:

Error Stack:

⨯ TypeError: Response body object should not be disturbed or locked
    at new NextRequest (.next/server/chunks/10404.js:4220:14)
    at NextRequestAdapter.fromNodeNextRequest (.next/server/chunks/10404.js:3770:16)
    at handler (.next/server/app/(backend)/trpc/lambda/[trpc]/route.js:618:127)

For chat endpoint:

⨯ TypeError: Response body object should not be disturbed or locked
    at new NextRequest (.next/server/chunks/10404.js:4220:14)
    at NextRequestAdapter.fromNodeNextRequest (.next/server/chunks/10404.js:3770:16)
    at handler (.next/server/app/(backend)/webapi/chat/[provider]/route.js:569:53)

Expected behavior:

Request body should be properly cloned and finalized before being passed to route handlers, with no race conditions causing stream state inconsistencies.

Provide environment information

Operating System:
  Platform: linux (Docker)
  Version: Docker image lobehub/lobe-chat:2.0.0-next.89
Binaries:
  Node: v20.x
  pnpm: 9.x
Relevant Packages:
  next: 16.0.1
  react: 19.0.0
  react-dom: 19.0.0
Next.js Config:
  output: standalone
  runtime: nodejs (middleware)

Which area(s) are affected?

Middleware, App Router, tRPC integration

Which stage(s) are affected?

Production (Docker standalone mode), also reproducible in development with sufficient load

Additional context

Background

We are running LobeChat (https://github.com/lobehub/lobe-chat) in production with Docker using Next.js 16's standalone output mode. We implemented the commonly suggested workaround of cloning requests in our auth middleware and tRPC handlers:

// Our attempted fix in auth middleware
export const checkAuth = (handler: RequestHandler) => async (req: Request, options: RequestOptions) => {
  const clonedReq = req.clone();  // This doesn't solve the issue
  // ...
  return handler(clonedReq, { ...options, jwtPayload });
};

// Our attempted fix in tRPC handlers
const handler = (req: NextRequest) => {
  const preparedReq = prepareRequestForTRPC(req);  // Using req.clone()
  return fetchRequestHandler({
    req: preparedReq,
    // ...
  });
};

However, this workaround does NOT solve the problem.

Root Cause Analysis

After investigating Next.js source code and related issues (#83453, #85416), we identified that the problem occurs in Next.js's internal body cloning mechanism:

The race condition happens here:

// packages/next/src/server/next-server.ts (Line ~1757)
} finally {
  if (hasRequestBody) {
    requestData.body.finalize();  // ❌ Missing await!
  }
}

The finalize() method is async but not awaited:

// packages/next/src/server/body-streams.ts
export interface CloneableBody {
  finalize(): Promise<void>  // Returns a Promise
  cloneBodyStream(): Readable
}

Timeline of the race condition:

T0: Request arrives, body cloning starts
    └─ cloneBodyStream() creates PassThrough streams

T1: Middleware processes with cloned stream

T2: Middleware completes
    └─ finalize() called WITHOUT await ❌

T3: Route handler immediately tries to read req.body
    └─ Original stream may still be transferring data

T4: finalize() still executing in background
    └─ replaceRequestBody() not yet completed

T5: undici detects disturbed stream state
    └─ Throws "Response body object should not be disturbed or locked"

Why Application-Level req.clone() Doesn't Work

The issue occurs before our application code runs:

  1. Next.js internally calls cloneBodyStream() when passing request to middleware
  2. The finalize() is called without await, leaving the stream in an inconsistent state
  3. By the time our req.clone() executes, the underlying stream is already corrupted
  4. Cloning a corrupted Request object doesn't fix the underlying stream issue

Reproduction Characteristics

  • Consistently fails: Large payloads (>350MB) or multiple concurrent requests
  • ⚠️ Intermittently fails: Medium payloads (50KB-350MB) depending on server load
  • Rarely fails: Small payloads (<50KB) due to fast stream completion
  • 📊 Hardware dependent: Threshold varies based on CPU/network performance (as noted in #85416)

Impact on Production

This issue causes:

  • Random 500 errors for end users
  • Failed chat completions mid-conversation
  • Failed model list fetches
  • Degraded user experience
  • No reliable workaround at application level

Related Issues & PR

  • #83453 - Original report with large file uploads
  • #85416 - Detailed race condition analysis
  • #85418 - PR with fix (merged to canary but not released)

The fix in PR #85418 is simple but critical:

} finally {
  if (hasRequestBody) {
-   requestData.body.finalize();
+   await requestData.body.finalize();
  }
}

Request

  1. Urgent release: Please include the fix from PR #85418 in the next patch release (16.0.2)
  2. Documentation: Add a warning in the migration guide about this issue
  3. Workaround guidance: Provide official guidance for users stuck on 16.0.1

Temporary Workaround

For others affected by this issue, the only reliable workaround is to patch Next.js:

Create .patches/[email protected]:

diff --git a/dist/server/next-server.js b/dist/server/next-server.js
--- a/dist/server/next-server.js
+++ b/dist/server/next-server.js
@@ -1241,7 +1241,7 @@ class NextNodeServer extends _baseserver.default {
                 });
             } finally{
                 if (hasRequestBody) {
-                    requestData.body.finalize();
+                    await requestData.body.finalize();
                 }
             }
         } else {

Reference in package.json:

{
  "pnpm": {
    "patchedDependencies": {
      "[email protected]": "patches/[email protected]"
    }
  }
}

Note: This issue affects any production application using:

  • Node.js runtime middleware (not Edge runtime)
  • Request body reading in route handlers
  • Frameworks like tRPC, Server Actions with file uploads, or custom API routes
  • Moderate to large request payloads

The fix is already merged but not yet released, causing production issues for many users who have upgraded to Next.js 16.

extent analysis

TL;DR

The most likely fix for the race condition issue in Next.js middleware body cloning is to apply the patch from PR #85418, which adds an await to the finalize() method call.

Guidance

  1. Apply the patch: Create a patch file with the fix from PR #85418 and reference it in your package.json to temporarily resolve the issue.
  2. Verify the fix: Test your application with moderate to large request payloads and concurrent requests to ensure the error no longer occurs.
  3. Monitor for official release: Wait for the official release of Next.js 16.0.2, which should include the fix from PR #85418, and upgrade to it as soon as possible.
  4. Check for related issues: Be aware of other potential issues related to request body cloning and handling in Next.js middleware.

Example

To apply the patch, create a file named [email protected] in a .patches directory with the following content:

diff --git a/dist/server/next-server.js b/dist/server/next-server.js
--- a/dist/server/next-server.js
+++ b/dist/server/next-server.js
@@ -1241,7 +1241,7 @@ class NextNodeServer extends _baseserver.default {
                 });
             } finally{
                 if (hasRequestBody) {
-                    requestData.body.finalize();
+                    await requestData.body.finalize();
                 }
             }
         } else {

Then, add the following to your package.json:

{
  "pnpm": {
    "patchedDependencies": {
      "[email protected]": "patches/[email protected]"
    }
  }
}

Notes

This fix is specific to Next.js 16.0.1 and may not apply to other versions. The official release of Next.js 16.0.2 should include the fix from PR #85418, making the patch unnecessary.

Recommendation

Apply the workaround by patching Next.js 16.0.1, as the official release with the fix is not yet available. This will ensure that your application is stable and functional until the official release is made.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING