fix(sse): rebuild deadline-wrapped request from public accessors - #14904
TrippyEngineer wants to merge 2 commits into
Conversation
withDeadlineSignal() wrapped the inbound request with
`new Request(request, { signal, headers })`. Next.js hands App Router
handlers that have no `dynamic` export a Proxy around the NextRequest
(proxyNextRequest), and the Request constructor reads the input's private
#state field, which cannot be reached through a Proxy. Every call to
/v1/chat/completions, /v1/messages and /v1/responses therefore failed with
"TypeError: Cannot read private member #state from an object whose class
did not declare it" and HTTP 500 (regression from diegosouzapw#14808).
Rebuild the request from its public accessors (url, method, headers,
redirect, body stream with duplex "half"), matching rebuildRequest() in
chatBodyAdmission. Adds regression tests with a Proxy-wrapped request and
a body-less GET.
|
Thanks @TrippyEngineer for the clear root-cause write-up on the #14808 regression. Next's #14886 (merged 2026-09-28) fixed this on If your extra assertions (such as |
Summary
/v1/chat/completions,/v1/messagesand/v1/responsesfailed with HTTP 500 onrelease/v3.8.51:TypeError: Cannot read private member #state from an object whose class did not declare itatwithDeadlineSignal(open-sse/utils/earlyStreamKeepalive.ts).dynamicexport aProxyaround theNextRequest(proxyNextRequestinnext/dist/server/route-modules/app-route/module.js).withDeadlineSignal()(added in fix(sse): bound slow keepalive path with absolute last-resort deadline #14808) wrapped it withnew Request(request, { signal, headers }); theRequestconstructor reads the input's private#state, which cannot be reached through a Proxy.url,method,headers,redirect, body stream withduplex: "half"), the same patternrebuildRequest()inchatBodyAdmission.tsalready uses. The deadline token header and combined signal are unchanged.Related Issues
Validation
node --import tsx/esm --test tests/unit/early-stream-keepalive.test.ts— 26/26 passeslint+prettieron changed files (lint-staged pre-commit, with repo suppressions)npm run typecheck:core— cleannpm run dev,POST /v1/chat/completionswent from HTTP 500 to reaching combo routingTests Added Or Updated
tests/unit/early-stream-keepalive.test.tswithDeadlineSignal accepts a Proxy-wrapped request (Next.js proxyNextRequest)— fails on the base with the exact production error, passes with the fix; also asserts method/url/headers/body are preserved, the deadline controller still resolves, and a client abort still reaches the wrapped signal.withDeadlineSignal keeps a body-less GET request valid.Coverage Notes
withDeadlineSignal) is covered by the new tests plus the existing deadline tests in the same file.Reviewer Notes
npm run dev, Turbopack). The Proxy is applied fordynamic=undefined/"auto", so production builds are affected too.