Skip to content

fix(sse): rebuild deadline-wrapped request from public accessors - #14904

Closed
TrippyEngineer wants to merge 2 commits into
diegosouzapw:release/v3.8.51from
TrippyEngineer:fix/deadline-signal-proxied-request
Closed

TrippyEngineer wants to merge 2 commits into
diegosouzapw:release/v3.8.51from
TrippyEngineer:fix/deadline-signal-proxied-request

Conversation

@TrippyEngineer

Copy link
Copy Markdown
Contributor

Summary

  • Every request to /v1/chat/completions, /v1/messages and /v1/responses failed with HTTP 500 on release/v3.8.51:
    TypeError: Cannot read private member #state from an object whose class did not declare it at withDeadlineSignal (open-sse/utils/earlyStreamKeepalive.ts).
  • Cause: Next.js hands App Router handlers that have no dynamic export a Proxy around the NextRequest (proxyNextRequest in next/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 with new Request(request, { signal, headers }); the Request constructor reads the input's private #state, which cannot be reached through a Proxy.
  • Fix: rebuild the wrapped request from its public accessors (url, method, headers, redirect, body stream with duplex: "half"), the same pattern rebuildRequest() in chatBodyAdmission.ts already uses. The deadline token header and combined signal are unchanged.

Related Issues

Validation

  • Change type: routing
  • Focused tests: node --import tsx/esm --test tests/unit/early-stream-keepalive.test.ts — 26/26 pass
  • eslint + prettier on changed files (lint-staged pre-commit, with repo suppressions)
  • npm run typecheck:core — clean
  • Production-code changes include a new automated test in this PR
  • Live check: npm run dev, POST /v1/chat/completions went from HTTP 500 to reaching combo routing

Tests Added Or Updated

  • tests/unit/early-stream-keepalive.test.ts
    • withDeadlineSignal 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

  • The single changed function (withDeadlineSignal) is covered by the new tests plus the existing deadline tests in the same file.

Reviewer Notes

  • Reproduced on Windows 11, Node 24.19.0, Next 16.3.5 (npm run dev, Turbopack). The Proxy is applied for dynamic = undefined/"auto", so production builds are affected too.

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.
@diegosouzapw

Copy link
Copy Markdown
Owner

Thanks @TrippyEngineer for the clear root-cause write-up on the #14808 regression. Next's proxyNextRequest wrapping plus new Request(request, init) reading #state is exactly what was sending those routes to 500.

#14886 (merged 2026-09-28) fixed this on release/v3.8.51. withDeadlineSignal in open-sse/utils/earlyStreamKeepalive.ts now rebuilds the request from its public accessors (url, method, headers, body, with duplex: "half") and keeps the deadline token header and the combined signal. tests/unit/early-stream-keepalive.test.ts covers the Proxy-wrapped case. Several near-identical fixes arrived within hours of each other and only one could land, so I'm closing this as covered.

If your extra assertions (such as redirect preservation or client-abort propagation through the wrapper) test something the tip suite doesn't, a small test-only PR would be welcome. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants