Skip to content

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

@arvinxx

Description

@arvinxx

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

Link to the code that reproduces this issue

lobehub/lobe-chat#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 Race condition with Node.js middleware body cloning #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

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 fix(nodejs-middleware): await for body cloning to be properly finalized #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/next@16.0.1.patch:

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": {
      "next@16.0.1": "patches/next@16.0.1.patch"
    }
  }
}

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    invalid linkThe issue was auto-closed due to a missing/invalid reproduction link. A new issue should be opened.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions