From a69d4a21f7f77a0c9c81ad0ff3e066f0502ea252 Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Sun, 21 Sep 2025 13:03:56 +0200 Subject: [PATCH 1/7] fix(node): Handle unhandled rejection for Vercel AI errors --- .../tracing/vercelai/instrumentation.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/packages/node/src/integrations/tracing/vercelai/instrumentation.ts b/packages/node/src/integrations/tracing/vercelai/instrumentation.ts index 872e0153edba..d059662b5188 100644 --- a/packages/node/src/integrations/tracing/vercelai/instrumentation.ts +++ b/packages/node/src/integrations/tracing/vercelai/instrumentation.ts @@ -202,12 +202,48 @@ export class SentryVercelAiInstrumentation extends InstrumentationBase { } } + /** + * Sets up global error handling for Vercel AI stream processing errors + */ + private _setupGlobalErrorHandling(): void { + // Add a global unhandled rejection handler specifically for Vercel AI errors + const originalHandler = process.listeners('unhandledRejection'); + const aiErrorHandler = (reason: unknown, promise: Promise): void => { + // Check if this is a Vercel AI error + if (reason && typeof reason === 'object' && reason !== null && Symbol.for('vercel.ai.error') in reason) { + // Add Sentry context to the error + if (reason && typeof reason === 'object') { + addNonEnumerableProperty(reason, '_sentry_active_span', getActiveSpan()); + } + + // Don't re-throw the error to prevent it from becoming unhandled + return; + } + + // For non-AI errors, let the original handler deal with it + if (originalHandler.length > 0) { + originalHandler.forEach(handler => { + if (typeof handler === 'function') { + handler(reason, promise); + } + }); + } + }; + + // Remove any existing unhandled rejection handlers and add our AI-specific one + process.removeAllListeners('unhandledRejection'); + process.on('unhandledRejection', aiErrorHandler); + } + /** * Patches module exports to enable Vercel AI telemetry. */ private _patch(moduleExports: PatchedModuleExports): unknown { this._isPatched = true; + // Set up global error handling for Vercel AI stream processing errors + this._setupGlobalErrorHandling(); + this._callbacks.forEach(callback => callback()); this._callbacks = []; From a9bbc90e4381bffc6f32bc25975f6205768ffe2c Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Mon, 22 Sep 2025 18:16:22 +0200 Subject: [PATCH 2/7] feat(node-core): Extend onnhandledrejection with ignore errors options --- .../src/integrations/onunhandledrejection.ts | 84 ++++++++++++++----- .../tracing/vercelai/instrumentation.ts | 35 -------- 2 files changed, 65 insertions(+), 54 deletions(-) diff --git a/packages/node-core/src/integrations/onunhandledrejection.ts b/packages/node-core/src/integrations/onunhandledrejection.ts index dbddb2a4c396..8e213a5bc080 100644 --- a/packages/node-core/src/integrations/onunhandledrejection.ts +++ b/packages/node-core/src/integrations/onunhandledrejection.ts @@ -4,21 +4,31 @@ import { logAndExitProcess } from '../utils/errorhandling'; type UnhandledRejectionMode = 'none' | 'warn' | 'strict'; +type IgnoreMatcher = { symbol: symbol } | { name?: string | RegExp; message?: string | RegExp }; + interface OnUnhandledRejectionOptions { /** * Option deciding what to do after capturing unhandledRejection, * that mimicks behavior of node's --unhandled-rejection flag. */ mode: UnhandledRejectionMode; + /** Rejection Errors to ignore (don't capture or warn). */ + ignore?: IgnoreMatcher[]; } const INTEGRATION_NAME = 'OnUnhandledRejection'; +const DEFAULT_IGNORES: IgnoreMatcher[] = [ + { + name: 'AI_NoOutputGeneratedError', // When stream aborts in Vercel AI SDK, flush() fails with an error + }, +]; + const _onUnhandledRejectionIntegration = ((options: Partial = {}) => { - const opts = { - mode: 'warn', - ...options, - } satisfies OnUnhandledRejectionOptions; + const opts: OnUnhandledRejectionOptions = { + mode: options.mode ?? 'warn', + ignore: [...DEFAULT_IGNORES, ...(options.ignore ?? [])], + }; return { name: INTEGRATION_NAME, @@ -28,31 +38,67 @@ const _onUnhandledRejectionIntegration = ((options: Partial; + const name = typeof errorLike.name === 'string' ? errorLike.name : ''; + const message = typeof errorLike.message === 'string' ? errorLike.message : String(reason); + + return { name, message, isObject }; +} + +/** Check if a matcher matches the reason */ +function checkMatcher( + matcher: IgnoreMatcher, + reason: unknown, + errorInfo: ReturnType, +): boolean { + if ('symbol' in matcher) { + return errorInfo.isObject && matcher.symbol in (reason as object); + } + + // name/message matcher + const nameMatches = + matcher.name === undefined || + (typeof matcher.name === 'string' ? errorInfo.name === matcher.name : matcher.name.test(errorInfo.name)); + + const messageMatches = + matcher.message === undefined || + (typeof matcher.message === 'string' + ? errorInfo.message.includes(matcher.message) + : matcher.message.test(errorInfo.message)); + + return nameMatches && messageMatches; +} + +/** Match helper */ +function matchesIgnore(reason: unknown, list: IgnoreMatcher[]): boolean { + const errorInfo = extractErrorInfo(reason); + return list.some(matcher => checkMatcher(matcher, reason, errorInfo)); +} + +/** Core handler */ export function makeUnhandledPromiseHandler( client: Client, options: OnUnhandledRejectionOptions, ): (reason: unknown, promise: unknown) => void { return function sendUnhandledPromise(reason: unknown, promise: unknown): void { - if (getClient() !== client) { - return; - } + // Only handle for the active client + if (getClient() !== client) return; + + // Skip if configured to ignore + if (matchesIgnore(reason, options.ignore ?? [])) return; const level: SeverityLevel = options.mode === 'strict' ? 'fatal' : 'error'; - // this can be set in places where we cannot reliably get access to the active span/error - // when the error bubbles up to this handler, we can use this to set the active span + // If upstream code stored an active span on the error, use it for linking. const activeSpanForError = reason && typeof reason === 'object' ? (reason as { _sentry_active_span?: Span })._sentry_active_span : undefined; diff --git a/packages/node/src/integrations/tracing/vercelai/instrumentation.ts b/packages/node/src/integrations/tracing/vercelai/instrumentation.ts index d059662b5188..9db2e0ff252e 100644 --- a/packages/node/src/integrations/tracing/vercelai/instrumentation.ts +++ b/packages/node/src/integrations/tracing/vercelai/instrumentation.ts @@ -202,38 +202,6 @@ export class SentryVercelAiInstrumentation extends InstrumentationBase { } } - /** - * Sets up global error handling for Vercel AI stream processing errors - */ - private _setupGlobalErrorHandling(): void { - // Add a global unhandled rejection handler specifically for Vercel AI errors - const originalHandler = process.listeners('unhandledRejection'); - const aiErrorHandler = (reason: unknown, promise: Promise): void => { - // Check if this is a Vercel AI error - if (reason && typeof reason === 'object' && reason !== null && Symbol.for('vercel.ai.error') in reason) { - // Add Sentry context to the error - if (reason && typeof reason === 'object') { - addNonEnumerableProperty(reason, '_sentry_active_span', getActiveSpan()); - } - - // Don't re-throw the error to prevent it from becoming unhandled - return; - } - - // For non-AI errors, let the original handler deal with it - if (originalHandler.length > 0) { - originalHandler.forEach(handler => { - if (typeof handler === 'function') { - handler(reason, promise); - } - }); - } - }; - - // Remove any existing unhandled rejection handlers and add our AI-specific one - process.removeAllListeners('unhandledRejection'); - process.on('unhandledRejection', aiErrorHandler); - } /** * Patches module exports to enable Vercel AI telemetry. @@ -241,9 +209,6 @@ export class SentryVercelAiInstrumentation extends InstrumentationBase { private _patch(moduleExports: PatchedModuleExports): unknown { this._isPatched = true; - // Set up global error handling for Vercel AI stream processing errors - this._setupGlobalErrorHandling(); - this._callbacks.forEach(callback => callback()); this._callbacks = []; From 403893cb2dc38a34b76989415086a3ff93051e78 Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Mon, 22 Sep 2025 18:20:19 +0200 Subject: [PATCH 3/7] extra line --- .../node/src/integrations/tracing/vercelai/instrumentation.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/node/src/integrations/tracing/vercelai/instrumentation.ts b/packages/node/src/integrations/tracing/vercelai/instrumentation.ts index 9db2e0ff252e..872e0153edba 100644 --- a/packages/node/src/integrations/tracing/vercelai/instrumentation.ts +++ b/packages/node/src/integrations/tracing/vercelai/instrumentation.ts @@ -202,7 +202,6 @@ export class SentryVercelAiInstrumentation extends InstrumentationBase { } } - /** * Patches module exports to enable Vercel AI telemetry. */ From e4dab91c20472f4952f195b1d102693ebda69363 Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Mon, 22 Sep 2025 18:21:19 +0200 Subject: [PATCH 4/7] one more comment to revert --- packages/node-core/src/integrations/onunhandledrejection.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/node-core/src/integrations/onunhandledrejection.ts b/packages/node-core/src/integrations/onunhandledrejection.ts index 8e213a5bc080..c95bdf819b55 100644 --- a/packages/node-core/src/integrations/onunhandledrejection.ts +++ b/packages/node-core/src/integrations/onunhandledrejection.ts @@ -98,7 +98,8 @@ export function makeUnhandledPromiseHandler( const level: SeverityLevel = options.mode === 'strict' ? 'fatal' : 'error'; - // If upstream code stored an active span on the error, use it for linking. + // this can be set in places where we cannot reliably get access to the active span/error + // when the error bubbles up to this handler, we can use this to set the active span const activeSpanForError = reason && typeof reason === 'object' ? (reason as { _sentry_active_span?: Span })._sentry_active_span : undefined; From 36ad33d9badabdecd943a795d436b31b7be10e96 Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Tue, 30 Sep 2025 18:20:03 +0300 Subject: [PATCH 5/7] rename fn --- packages/node-core/src/integrations/onunhandledrejection.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/node-core/src/integrations/onunhandledrejection.ts b/packages/node-core/src/integrations/onunhandledrejection.ts index c95bdf819b55..90d0ad10710a 100644 --- a/packages/node-core/src/integrations/onunhandledrejection.ts +++ b/packages/node-core/src/integrations/onunhandledrejection.ts @@ -20,7 +20,7 @@ const INTEGRATION_NAME = 'OnUnhandledRejection'; const DEFAULT_IGNORES: IgnoreMatcher[] = [ { - name: 'AI_NoOutputGeneratedError', // When stream aborts in Vercel AI SDK, flush() fails with an error + name: 'AI_NoOutputGeneratedError', // When stream aborts in Vercel AI SDK, Vercel flush() fails with an error }, ]; @@ -55,7 +55,7 @@ function extractErrorInfo(reason: unknown): { name: string; message: string; isO } /** Check if a matcher matches the reason */ -function checkMatcher( +function isMatchingReason( matcher: IgnoreMatcher, reason: unknown, errorInfo: ReturnType, @@ -81,7 +81,7 @@ function checkMatcher( /** Match helper */ function matchesIgnore(reason: unknown, list: IgnoreMatcher[]): boolean { const errorInfo = extractErrorInfo(reason); - return list.some(matcher => checkMatcher(matcher, reason, errorInfo)); + return list.some(matcher => isMatchingReason(matcher, reason, errorInfo)); } /** Core handler */ From 62bbdcd3411e977d3ae1b81d1e489c5341c8bcd5 Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Tue, 30 Sep 2025 19:15:02 +0300 Subject: [PATCH 6/7] add tests --- .../ignore-custom-name.js | 27 ++++++++++++ .../ignore-custom-symbol.js | 30 +++++++++++++ .../ignore-default.js | 22 ++++++++++ .../onUnhandledRejectionIntegration/test.ts | 42 +++++++++++++++++++ 4 files changed, 121 insertions(+) create mode 100644 dev-packages/node-integration-tests/suites/public-api/onUnhandledRejectionIntegration/ignore-custom-name.js create mode 100644 dev-packages/node-integration-tests/suites/public-api/onUnhandledRejectionIntegration/ignore-custom-symbol.js create mode 100644 dev-packages/node-integration-tests/suites/public-api/onUnhandledRejectionIntegration/ignore-default.js diff --git a/dev-packages/node-integration-tests/suites/public-api/onUnhandledRejectionIntegration/ignore-custom-name.js b/dev-packages/node-integration-tests/suites/public-api/onUnhandledRejectionIntegration/ignore-custom-name.js new file mode 100644 index 000000000000..7ff548624e5f --- /dev/null +++ b/dev-packages/node-integration-tests/suites/public-api/onUnhandledRejectionIntegration/ignore-custom-name.js @@ -0,0 +1,27 @@ +const Sentry = require('@sentry/node'); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [ + Sentry.onUnhandledRejectionIntegration({ + // Use default mode: 'warn' - integration is active but should ignore CustomIgnoredError + ignore: [{ name: 'CustomIgnoredError' }], + }), + ], +}); + +// Create a custom error that should be ignored +class CustomIgnoredError extends Error { + constructor(message) { + super(message); + this.name = 'CustomIgnoredError'; + } +} + +setTimeout(() => { + process.stdout.write("I'm alive!"); + process.exit(0); +}, 500); + +// This should be ignored by the custom ignore matcher and not produce a warning +Promise.reject(new CustomIgnoredError('This error should be ignored')); diff --git a/dev-packages/node-integration-tests/suites/public-api/onUnhandledRejectionIntegration/ignore-custom-symbol.js b/dev-packages/node-integration-tests/suites/public-api/onUnhandledRejectionIntegration/ignore-custom-symbol.js new file mode 100644 index 000000000000..4c24b9138158 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/public-api/onUnhandledRejectionIntegration/ignore-custom-symbol.js @@ -0,0 +1,30 @@ +const Sentry = require('@sentry/node'); + +const IGNORE_SYMBOL = Symbol('ignore'); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [ + Sentry.onUnhandledRejectionIntegration({ + // Use default mode: 'warn' - integration is active but should ignore errors with the symbol + ignore: [{ symbol: IGNORE_SYMBOL }], + }), + ], +}); + +// Create an error with the ignore symbol +class CustomError extends Error { + constructor(message) { + super(message); + this.name = 'CustomError'; + this[IGNORE_SYMBOL] = true; + } +} + +setTimeout(() => { + process.stdout.write("I'm alive!"); + process.exit(0); +}, 500); + +// This should be ignored by the symbol matcher and not produce a warning +Promise.reject(new CustomError('This error should be ignored by symbol')); diff --git a/dev-packages/node-integration-tests/suites/public-api/onUnhandledRejectionIntegration/ignore-default.js b/dev-packages/node-integration-tests/suites/public-api/onUnhandledRejectionIntegration/ignore-default.js new file mode 100644 index 000000000000..623aa8eaa8f7 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/public-api/onUnhandledRejectionIntegration/ignore-default.js @@ -0,0 +1,22 @@ +const Sentry = require('@sentry/node'); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + // Use default mode: 'warn' - integration is active but should ignore AI_NoOutputGeneratedError +}); + +// Create an error with the name that should be ignored by default +class AI_NoOutputGeneratedError extends Error { + constructor(message) { + super(message); + this.name = 'AI_NoOutputGeneratedError'; + } +} + +setTimeout(() => { + process.stdout.write("I'm alive!"); + process.exit(0); +}, 500); + +// This should be ignored by default and not produce a warning +Promise.reject(new AI_NoOutputGeneratedError('Stream aborted')); diff --git a/dev-packages/node-integration-tests/suites/public-api/onUnhandledRejectionIntegration/test.ts b/dev-packages/node-integration-tests/suites/public-api/onUnhandledRejectionIntegration/test.ts index d3c8b4d599ff..b7355c1dfa41 100644 --- a/dev-packages/node-integration-tests/suites/public-api/onUnhandledRejectionIntegration/test.ts +++ b/dev-packages/node-integration-tests/suites/public-api/onUnhandledRejectionIntegration/test.ts @@ -178,4 +178,46 @@ test rejection`); expect(transactionEvent!.contexts!.trace!.trace_id).toBe(errorEvent!.contexts!.trace!.trace_id); expect(transactionEvent!.contexts!.trace!.span_id).toBe(errorEvent!.contexts!.trace!.span_id); }); + + test('should not warn when AI_NoOutputGeneratedError is rejected (default ignore)', () => + new Promise(done => { + expect.assertions(3); + + const testScriptPath = path.resolve(__dirname, 'ignore-default.js'); + + childProcess.execFile('node', [testScriptPath], { encoding: 'utf8' }, (err, stdout, stderr) => { + expect(err).toBeNull(); + expect(stdout).toBe("I'm alive!"); + expect(stderr).toBe(''); // No warning should be shown + done(); + }); + })); + + test('should not warn when custom ignored error by name is rejected', () => + new Promise(done => { + expect.assertions(3); + + const testScriptPath = path.resolve(__dirname, 'ignore-custom-name.js'); + + childProcess.execFile('node', [testScriptPath], { encoding: 'utf8' }, (err, stdout, stderr) => { + expect(err).toBeNull(); + expect(stdout).toBe("I'm alive!"); + expect(stderr).toBe(''); // No warning should be shown + done(); + }); + })); + + test('should not warn when custom ignored error by symbol is rejected', () => + new Promise(done => { + expect.assertions(3); + + const testScriptPath = path.resolve(__dirname, 'ignore-custom-symbol.js'); + + childProcess.execFile('node', [testScriptPath], { encoding: 'utf8' }, (err, stdout, stderr) => { + expect(err).toBeNull(); + expect(stdout).toBe("I'm alive!"); + expect(stderr).toBe(''); // No warning should be shown + done(); + }); + })); }); From d5352c9cb0a586be3ada2e0c57ea4cabfcc52bb2 Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Wed, 1 Oct 2025 15:19:42 +0300 Subject: [PATCH 7/7] quick refactors --- .../ignore-custom-symbol.js | 30 ----------- .../onUnhandledRejectionIntegration/test.ts | 14 ----- .../src/integrations/onunhandledrejection.ts | 53 +++++++++---------- 3 files changed, 25 insertions(+), 72 deletions(-) delete mode 100644 dev-packages/node-integration-tests/suites/public-api/onUnhandledRejectionIntegration/ignore-custom-symbol.js diff --git a/dev-packages/node-integration-tests/suites/public-api/onUnhandledRejectionIntegration/ignore-custom-symbol.js b/dev-packages/node-integration-tests/suites/public-api/onUnhandledRejectionIntegration/ignore-custom-symbol.js deleted file mode 100644 index 4c24b9138158..000000000000 --- a/dev-packages/node-integration-tests/suites/public-api/onUnhandledRejectionIntegration/ignore-custom-symbol.js +++ /dev/null @@ -1,30 +0,0 @@ -const Sentry = require('@sentry/node'); - -const IGNORE_SYMBOL = Symbol('ignore'); - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [ - Sentry.onUnhandledRejectionIntegration({ - // Use default mode: 'warn' - integration is active but should ignore errors with the symbol - ignore: [{ symbol: IGNORE_SYMBOL }], - }), - ], -}); - -// Create an error with the ignore symbol -class CustomError extends Error { - constructor(message) { - super(message); - this.name = 'CustomError'; - this[IGNORE_SYMBOL] = true; - } -} - -setTimeout(() => { - process.stdout.write("I'm alive!"); - process.exit(0); -}, 500); - -// This should be ignored by the symbol matcher and not produce a warning -Promise.reject(new CustomError('This error should be ignored by symbol')); diff --git a/dev-packages/node-integration-tests/suites/public-api/onUnhandledRejectionIntegration/test.ts b/dev-packages/node-integration-tests/suites/public-api/onUnhandledRejectionIntegration/test.ts index b7355c1dfa41..cd0627664ea3 100644 --- a/dev-packages/node-integration-tests/suites/public-api/onUnhandledRejectionIntegration/test.ts +++ b/dev-packages/node-integration-tests/suites/public-api/onUnhandledRejectionIntegration/test.ts @@ -199,20 +199,6 @@ test rejection`); const testScriptPath = path.resolve(__dirname, 'ignore-custom-name.js'); - childProcess.execFile('node', [testScriptPath], { encoding: 'utf8' }, (err, stdout, stderr) => { - expect(err).toBeNull(); - expect(stdout).toBe("I'm alive!"); - expect(stderr).toBe(''); // No warning should be shown - done(); - }); - })); - - test('should not warn when custom ignored error by symbol is rejected', () => - new Promise(done => { - expect.assertions(3); - - const testScriptPath = path.resolve(__dirname, 'ignore-custom-symbol.js'); - childProcess.execFile('node', [testScriptPath], { encoding: 'utf8' }, (err, stdout, stderr) => { expect(err).toBeNull(); expect(stdout).toBe("I'm alive!"); diff --git a/packages/node-core/src/integrations/onunhandledrejection.ts b/packages/node-core/src/integrations/onunhandledrejection.ts index 90d0ad10710a..42a17e2e6c7e 100644 --- a/packages/node-core/src/integrations/onunhandledrejection.ts +++ b/packages/node-core/src/integrations/onunhandledrejection.ts @@ -1,10 +1,17 @@ import type { Client, IntegrationFn, SeverityLevel, Span } from '@sentry/core'; -import { captureException, consoleSandbox, defineIntegration, getClient, withActiveSpan } from '@sentry/core'; +import { + captureException, + consoleSandbox, + defineIntegration, + getClient, + isMatchingPattern, + withActiveSpan, +} from '@sentry/core'; import { logAndExitProcess } from '../utils/errorhandling'; type UnhandledRejectionMode = 'none' | 'warn' | 'strict'; -type IgnoreMatcher = { symbol: symbol } | { name?: string | RegExp; message?: string | RegExp }; +type IgnoreMatcher = { name?: string | RegExp; message?: string | RegExp }; interface OnUnhandledRejectionOptions { /** @@ -41,47 +48,33 @@ const _onUnhandledRejectionIntegration = ((options: Partial; const name = typeof errorLike.name === 'string' ? errorLike.name : ''; const message = typeof errorLike.message === 'string' ? errorLike.message : String(reason); - return { name, message, isObject }; + return { name, message }; } /** Check if a matcher matches the reason */ -function isMatchingReason( - matcher: IgnoreMatcher, - reason: unknown, - errorInfo: ReturnType, -): boolean { - if ('symbol' in matcher) { - return errorInfo.isObject && matcher.symbol in (reason as object); - } - +function isMatchingReason(matcher: IgnoreMatcher, errorInfo: ReturnType): boolean { // name/message matcher - const nameMatches = - matcher.name === undefined || - (typeof matcher.name === 'string' ? errorInfo.name === matcher.name : matcher.name.test(errorInfo.name)); + const nameMatches = matcher.name === undefined || isMatchingPattern(errorInfo.name, matcher.name, true); - const messageMatches = - matcher.message === undefined || - (typeof matcher.message === 'string' - ? errorInfo.message.includes(matcher.message) - : matcher.message.test(errorInfo.message)); + const messageMatches = matcher.message === undefined || isMatchingPattern(errorInfo.message, matcher.message); return nameMatches && messageMatches; } /** Match helper */ -function matchesIgnore(reason: unknown, list: IgnoreMatcher[]): boolean { +function matchesIgnore(list: IgnoreMatcher[], reason: unknown): boolean { const errorInfo = extractErrorInfo(reason); - return list.some(matcher => isMatchingReason(matcher, reason, errorInfo)); + return list.some(matcher => isMatchingReason(matcher, errorInfo)); } /** Core handler */ @@ -91,10 +84,14 @@ export function makeUnhandledPromiseHandler( ): (reason: unknown, promise: unknown) => void { return function sendUnhandledPromise(reason: unknown, promise: unknown): void { // Only handle for the active client - if (getClient() !== client) return; + if (getClient() !== client) { + return; + } // Skip if configured to ignore - if (matchesIgnore(reason, options.ignore ?? [])) return; + if (matchesIgnore(options.ignore ?? [], reason)) { + return; + } const level: SeverityLevel = options.mode === 'strict' ? 'fatal' : 'error';