Skip to content

Commit 06f4e3c

Browse files
committed
fix(backend,tanstack-react-start): omit cross-realm AbortSignal in proxy and patchRequest
1 parent d0bffa3 commit 06f4e3c

4 files changed

Lines changed: 20 additions & 6 deletions

File tree

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
---
22
'@clerk/backend': patch
33
'@clerk/react-router': patch
4+
'@clerk/tanstack-react-start': patch
45
---
56

6-
Fix `Request` cloning to omit cross-realm `AbortSignal`. Node 24's bundled undici tightened the `instanceof AbortSignal` check on `RequestInit.signal`, which broke cloning of framework-specific requests such as `NextRequest` (in `@clerk/backend`'s `ClerkRequest`) and any subclassed Request passed through `patchRequest` (in `@clerk/react-router`).
7+
Fix `Request` cloning and outbound `fetch` to omit cross-realm `AbortSignal`. Node 24's bundled undici tightened the `instanceof AbortSignal` check on `RequestInit.signal`, which broke:
8+
9+
- Cloning framework-specific requests such as `NextRequest` in `@clerk/backend`'s `ClerkRequest`.
10+
- Subclassed `Request`s passed through `patchRequest` in `@clerk/react-router` and `@clerk/tanstack-react-start`.
11+
- Frontend API proxying in `@clerk/backend`'s `clerkFrontendApiProxy`, which forwarded the inbound request's signal to the upstream `fetch`. Abort propagation will be restored in a follow-up via an in-realm `AbortController` bridge.

packages/backend/src/__tests__/proxy.test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,11 @@ describe('proxy', () => {
572572
expect(response.status).toBe(200);
573573
});
574574

575-
it('propagates abort signal to upstream fetch', async () => {
575+
it('omits signal from upstream fetch (Node 24 undici cross-realm AbortSignal)', async () => {
576+
// Node 24's bundled undici tightened the instanceof AbortSignal check on
577+
// RequestInit.signal, which throws on cross-realm signals carried by
578+
// framework Request subclasses. Until we bridge abort propagation via an
579+
// in-realm AbortController, the signal is intentionally omitted.
576580
const mockResponse = new Response(JSON.stringify({}), { status: 200 });
577581
mockFetch.mockResolvedValue(mockResponse);
578582

@@ -587,7 +591,7 @@ describe('proxy', () => {
587591
});
588592

589593
const [, options] = mockFetch.mock.calls[0];
590-
expect(options.signal).toBe(request.signal);
594+
expect(options.signal).toBeUndefined();
591595
});
592596

593597
it('includes Cache-Control: no-store on error responses', async () => {

packages/backend/src/proxy.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,12 +297,15 @@ export async function clerkFrontendApiProxy(request: Request, options?: Frontend
297297

298298
try {
299299
// Make the proxied request
300-
// TODO: Consider adding AbortSignal.timeout(30_000) via AbortSignal.any()
300+
// TODO: Restore abort cascade via an in-realm AbortController bridge,
301+
// and consider adding AbortSignal.timeout(30_000) via AbortSignal.any().
302+
// `request.signal` is intentionally omitted: Node 24's bundled undici
303+
// tightened the instanceof AbortSignal check on RequestInit.signal, which
304+
// rejects cross-realm signals carried by framework Request subclasses.
301305
const fetchOptions: RequestInit = {
302306
method: request.method,
303307
headers,
304308
redirect: 'manual',
305-
signal: request.signal,
306309
};
307310

308311
// Only set duplex when body is present (required for streaming bodies)

packages/tanstack-react-start/src/server/utils/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,14 @@ export function getResponseClerkState(requestState: RequestState, additionalStat
6969
* @internal
7070
*/
7171
export const patchRequest = (request: Request) => {
72+
// Omit `signal` from the clone: Node 24's bundled undici tightened the
73+
// instanceof AbortSignal check, which rejects cross-realm signals (e.g.
74+
// those carried by framework Request subclasses).
7275
const clonedRequest = new Request(request.url, {
7376
headers: request.headers,
7477
method: request.method,
7578
redirect: request.redirect,
7679
cache: request.cache,
77-
signal: request.signal,
7880
});
7981

8082
// If duplex is not set, set it to 'half' to avoid duplex issues with unidici

0 commit comments

Comments
 (0)