Skip to content

Commit e0a63f9

Browse files
authored
chore(repo): upgrade monorepo to Node.js 24 (#8351)
1 parent a6a721d commit e0a63f9

13 files changed

Lines changed: 67 additions & 23 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@clerk/backend': patch
3+
'@clerk/react-router': patch
4+
'@clerk/tanstack-react-start': patch
5+
---
6+
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.

.github/actions/init-blacksmith/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ inputs:
44
node-version:
55
description: 'The node version to use'
66
required: false
7-
default: '22'
7+
default: '24.15.0'
88
playwright-enabled:
99
description: 'Enable Playwright?'
1010
required: false

.github/actions/init/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ inputs:
44
node-version:
55
description: 'The node version to use'
66
required: false
7-
default: '22'
7+
default: '24.15.0'
88
playwright-enabled:
99
description: 'Enable Playwright?'
1010
required: false

.github/workflows/ci.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,10 @@ jobs:
218218
fail-fast: false
219219
matrix:
220220
include:
221-
- node-version: 22
221+
- node-version: 24.15.0
222+
test-filter: "**"
223+
filter-label: "**"
224+
- node-version: 20.19.0
222225
test-filter: "**"
223226
filter-label: "**"
224227

@@ -257,7 +260,7 @@ jobs:
257260
- name: Run Typedoc tests
258261
run: |
259262
# Only run Typedoc tests for one matrix version and main test run
260-
if [ "${{ matrix.node-version }}" == "22" ] && [ "${{ matrix.test-filter }}" = "**" ]; then
263+
if [ "${{ matrix.node-version }}" == "24.15.0" ] && [ "${{ matrix.test-filter }}" = "**" ]; then
261264
pnpm turbo run //#test:typedoc
262265
fi
263266
env:
@@ -501,7 +504,7 @@ jobs:
501504
uses: ./.github/actions/init-blacksmith
502505
with:
503506
turbo-enabled: true
504-
node-version: 22
507+
node-version: 24.15.0
505508
turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
506509
turbo-summarize: ${{ env.TURBO_SUMMARIZE }}
507510
turbo-team: ${{ vars.TURBO_TEAM }}

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,7 @@ jobs:
524524

525525
strategy:
526526
matrix:
527-
version: [22] # NOTE: 18 is cached in the main release workflow
527+
version: [24] # NOTE: 20 is cached in the main release workflow
528528

529529
steps:
530530
- name: Checkout Repo

.nvmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
22.11.0
1+
24.15.0

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@
152152
},
153153
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319",
154154
"engines": {
155-
"node": ">=22.11.0",
155+
"node": ">=24.15.0",
156156
"pnpm": ">=10.33.0"
157157
},
158158
"pnpm": {

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/backend/src/tokens/clerkRequest.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,25 @@ class ClerkRequest extends Request {
2626
// https://github.com/nodejs/undici/issues/2155
2727
// https://github.com/nodejs/undici/blob/7153a1c78d51840bbe16576ce353e481c3934701/lib/fetch/request.js#L854
2828
const url = typeof input !== 'string' && 'url' in input ? input.url : String(input);
29-
super(url, init || typeof input === 'string' ? undefined : input);
29+
// When cloning a Request by passing it as init, hide its `signal`. Undici's
30+
// Request constructor in Node 24 performs a strict instanceof check on the
31+
// signal and rejects ones from a different realm (e.g. NextRequest). Using a
32+
// Proxy keeps property access lazy so environments that don't implement
33+
// optional getters (e.g. Cloudflare Workers' Request lacks `cache`) still work.
34+
let cloneInit: RequestInit | undefined;
35+
if (init) {
36+
cloneInit = init;
37+
} else if (typeof input !== 'string') {
38+
cloneInit = new Proxy(input as Request, {
39+
get(target, prop) {
40+
if (prop === 'signal') {
41+
return undefined;
42+
}
43+
return Reflect.get(target, prop, target);
44+
},
45+
}) as unknown as RequestInit;
46+
}
47+
super(url, cloneInit);
3048
this.clerkUrl = this.deriveUrlFromHeaders(this);
3149
this.cookies = this.parseCookies(this);
3250
}

0 commit comments

Comments
 (0)