From 8b1a55c74c87883e18e4185b5cddcda625b31549 Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Thu, 24 Sep 2026 17:13:17 +0300 Subject: [PATCH 1/2] fix(server): make custom AbortSignal pass Node AbortSignal.any brand check Node 26+ validates AbortSignal.any inputs with a private #brand field (nodejs/node#54965). Always expose ensureNativeCtrl().signal so duck-typed Proxies are no longer rejected. --- .changeset/abortsignal-any-brand.md | 5 +++++ packages/server/src/utils.ts | 16 ++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) create mode 100644 .changeset/abortsignal-any-brand.md diff --git a/.changeset/abortsignal-any-brand.md b/.changeset/abortsignal-any-brand.md new file mode 100644 index 00000000000..b7f4cbac2fc --- /dev/null +++ b/.changeset/abortsignal-any-brand.md @@ -0,0 +1,5 @@ +--- +'@whatwg-node/server': patch +--- + +Make `createCustomAbortControllerSignal().signal` always return a native `AbortSignal` so it passes Node.js's `#brand` check in `AbortSignal.any` (Node 26+). diff --git a/packages/server/src/utils.ts b/packages/server/src/utils.ts index c9b0d38d9a7..25040254ebd 100644 --- a/packages/server/src/utils.ts +++ b/packages/server/src/utils.ts @@ -785,19 +785,19 @@ class CustomAbortControllerSignal extends EventTarget implements AbortSignal, Ab } abort(reason?: any) { - if (this._nativeCtrl?.abort) { - return this._nativeCtrl?.abort(reason); - } + // Always abort via the native controller so dependents created with + // AbortSignal.any (which brand-checks for Node's private #brand) observe + // the abort. See https://github.com/nodejs/node/pull/54965 + const nativeCtrl = this.ensureNativeCtrl(); this._reason = reason || new DOMException('This operation was aborted', 'AbortError'); this.aborted = true; - this.dispatchEvent(new Event('abort')); + return nativeCtrl.abort(reason); } get signal(): AbortSignal { - if (this._nativeCtrl?.signal) { - return this._nativeCtrl.signal; - } - return this; + // Must return a real AbortSignal: Node's AbortSignal.any validates with + // `#brand in value` (private field), which Proxies / duck-types cannot pass. + return this.ensureNativeCtrl().signal; } get reason(): any { From 321d42c325a0a90d2f7af2956cf131d109af6bab Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Thu, 24 Sep 2026 17:15:47 +0300 Subject: [PATCH 2/2] docs: cite nodejs/node#65846 (v26.10) for AbortSignal brand check #54965 was closed unmerged; the #brand check landed via #65846 in v26.10.0. --- .changeset/abortsignal-any-brand.md | 2 +- packages/server/src/utils.ts | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.changeset/abortsignal-any-brand.md b/.changeset/abortsignal-any-brand.md index b7f4cbac2fc..49483855980 100644 --- a/.changeset/abortsignal-any-brand.md +++ b/.changeset/abortsignal-any-brand.md @@ -2,4 +2,4 @@ '@whatwg-node/server': patch --- -Make `createCustomAbortControllerSignal().signal` always return a native `AbortSignal` so it passes Node.js's `#brand` check in `AbortSignal.any` (Node 26+). +Make `createCustomAbortControllerSignal().signal` always return a native `AbortSignal` so it passes Node.js's `#brand` check in `AbortSignal.any` (Node v26.10+, nodejs/node#65846). diff --git a/packages/server/src/utils.ts b/packages/server/src/utils.ts index 25040254ebd..4eefc7774f9 100644 --- a/packages/server/src/utils.ts +++ b/packages/server/src/utils.ts @@ -786,8 +786,8 @@ class CustomAbortControllerSignal extends EventTarget implements AbortSignal, Ab abort(reason?: any) { // Always abort via the native controller so dependents created with - // AbortSignal.any (which brand-checks for Node's private #brand) observe - // the abort. See https://github.com/nodejs/node/pull/54965 + // AbortSignal.any (Node's private #brand check) observe the abort. + // See https://github.com/nodejs/node/pull/65846 (landed in v26.10.0). const nativeCtrl = this.ensureNativeCtrl(); this._reason = reason || new DOMException('This operation was aborted', 'AbortError'); this.aborted = true; @@ -795,8 +795,9 @@ class CustomAbortControllerSignal extends EventTarget implements AbortSignal, Ab } get signal(): AbortSignal { - // Must return a real AbortSignal: Node's AbortSignal.any validates with - // `#brand in value` (private field), which Proxies / duck-types cannot pass. + // Must return a real AbortSignal: since Node v26.10.0, AbortSignal.any + // validates with `#brand in value` (nodejs/node#65846). Proxies that only + // spoof getPrototypeOf no longer pass. return this.ensureNativeCtrl().signal; }