-
Notifications
You must be signed in to change notification settings - Fork 29.9k
Description
Race condition in Node.js middleware body cloning still causing production errors despite PR #85418
Link to the code that reproduces this issue
To Reproduce
- Deploy a Next.js 16 application with Node.js runtime middleware
- Use tRPC or similar frameworks that read request body in route handlers
- Send requests with moderate-sized payloads (>50KB) or trigger multiple tool calls
- 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:
- Next.js internally calls
cloneBodyStream()when passing request to middleware - The
finalize()is called withoutawait, leaving the stream in an inconsistent state - By the time our
req.clone()executes, the underlying stream is already corrupted - 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
- Response body object should not be disturbed or locked" error with Node.js middleware and large request bodies #83453 - Original report with large file uploads
- Race condition with Node.js middleware body cloning #85416 - Detailed race condition analysis
- fix(nodejs-middleware): await for body cloning to be properly finalized #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
- 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)
- Documentation: Add a warning in the migration guide about this issue
- 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.