diff --git a/.changeset/abortsignal-any-brand.md b/.changeset/abortsignal-any-brand.md new file mode 100644 index 00000000000..49483855980 --- /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 v26.10+, nodejs/node#65846). diff --git a/packages/server/src/utils.ts b/packages/server/src/utils.ts index c9b0d38d9a7..4eefc7774f9 100644 --- a/packages/server/src/utils.ts +++ b/packages/server/src/utils.ts @@ -785,19 +785,20 @@ 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 (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; - 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: 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; } get reason(): any {