From 34c50a3255499fff4e28c9927b3387c84d703698 Mon Sep 17 00:00:00 2001 From: Mradul Singh Date: Fri, 13 Nov 2026 15:13:49 +0530 Subject: [PATCH 1/2] fix(core): Always return a string from `_enhanceErrorWithSentryInfo` An Error whose `message` is not a string (a number, an object, a function) used to end up as a non-string `exception.value`. Event processors that call string methods on it then threw, and the event was dropped, e.g. `DropReactControlFlowErrors` in @sentry/nextjs. Co-Authored-By: Claude Opus 5.5 --- packages/core/src/utils/eventbuilder.ts | 29 ++++++++++++++--- .../core/test/lib/utils/eventbuilder.test.ts | 32 +++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/packages/core/src/utils/eventbuilder.ts b/packages/core/src/utils/eventbuilder.ts index 9b300c59c593..fd4a2b053d93 100644 --- a/packages/core/src/utils/eventbuilder.ts +++ b/packages/core/src/utils/eventbuilder.ts @@ -7,10 +7,11 @@ import type { ParameterizedString } from '../types/parameterize'; import type { SeverityLevel } from '../types/severity'; import type { StackFrame } from '../types/stackframe'; import type { StackParser } from '../types/stacktrace'; -import { isError, isErrorEvent, isParameterizedString, isPlainObject } from './is'; +import { isError, isErrorEvent, isParameterizedString, isPlainObject, isPrimitive } from './is'; import { addExceptionMechanism, addExceptionTypeValue } from './misc'; -import { normalizeToSize } from './normalize'; +import { normalizeToSize, stringifyValue } from './normalize'; import { extractExceptionKeysForMessage } from './object'; +import { stringify } from './string'; /** * Extracts stack frames from the error.stack string @@ -32,13 +33,33 @@ function hasSentryFetchUrlHost(error: unknown): error is Error & { __sentry_fetc * @hidden */ export function _enhanceErrorWithSentryInfo(error: T): string { + const message = errorMessageToString(error.message); + // If the error has a __sentry_fetch_url_host__ property (added by fetch instrumentation), // enhance the error message with the hostname. if (hasSentryFetchUrlHost(error)) { - return `${error.message} (${error.__sentry_fetch_url_host__})`; + return `${message} (${error.__sentry_fetch_url_host__})`; + } + + return message; +} + +// `message` is typed as a string, but user code can assign anything to it, and event processors +// call string methods on `exception.value`. +function errorMessageToString(message: unknown): string { + if (typeof message === 'string') { + return message; + } + + if (message == null) { + return ''; + } + + if (isPrimitive(message)) { + return String(message); } - return error.message; + return stringify(message, '') || stringifyValue(undefined, message); } /** diff --git a/packages/core/test/lib/utils/eventbuilder.test.ts b/packages/core/test/lib/utils/eventbuilder.test.ts index 2a08f073117e..ea0d2bb6aae3 100644 --- a/packages/core/test/lib/utils/eventbuilder.test.ts +++ b/packages/core/test/lib/utils/eventbuilder.test.ts @@ -308,5 +308,37 @@ describe('eventFromMessage', () => { const exception = exceptionFromError(stackParser, error); expect(exception.value).toBe('Failed to fetch (localhost:8080)'); }); + + it('should enhance a non-string message', () => { + const error = new TypeError('placeholder'); + (error as { message: unknown }).message = { code: 42 }; + addNonEnumerableProperty(error, '__sentry_fetch_url_host__', 'example.com'); + + const exception = exceptionFromError(stackParser, error); + expect(exception.value).toBe('{"code":42} (example.com)'); + }); + }); +}); + +describe('exceptionFromError', () => { + const circular: Record = { code: 42 }; + circular.self = circular; + + it.each([ + ['a number', 42, '42'], + ['a bigint', BigInt(42), '42'], + ['a boolean', false, 'false'], + ['undefined', undefined, ''], + ['null', null, ''], + ['an object', { code: 42 }, '{"code":42}'], + ['an array', [1, 'two'], '[1,"two"]'], + ['a circular object', circular, '[object Object]'], + ['a function', function namedFn() {}, '[Function: namedFn]'], + ])('turns %s into a string', (_, message, expected) => { + const error = new Error('placeholder'); + (error as { message: unknown }).message = message; + + const exception = exceptionFromError(stackParser, error); + expect(exception.value).toBe(expected); }); }); From 2c489dce42e953c4db37926bc58cb0a94a87f7b5 Mon Sep 17 00:00:00 2001 From: Martin Sonnberger Date: Fri, 13 Nov 2026 10:59:50 +0100 Subject: [PATCH 2/2] fix(core): Only JSON-stringify plain object and array error messages Other objects go through stringifyValue, so an Error, Map or DOM Event as the message no longer becomes '{}' or '{"isTrusted":true}'. Co-Authored-By: Claude Opus 5.5 --- packages/core/src/utils/eventbuilder.ts | 9 ++++++++- packages/core/test/lib/utils/eventbuilder.test.ts | 9 +++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/core/src/utils/eventbuilder.ts b/packages/core/src/utils/eventbuilder.ts index fd4a2b053d93..2e8445ed5878 100644 --- a/packages/core/src/utils/eventbuilder.ts +++ b/packages/core/src/utils/eventbuilder.ts @@ -59,7 +59,14 @@ function errorMessageToString(message: unknown): string { return String(message); } - return stringify(message, '') || stringifyValue(undefined, message); + if (isPlainObject(message) || Array.isArray(message)) { + const json = stringify(message, ''); + if (json) { + return json; + } + } + + return stringifyValue(undefined, message); } /** diff --git a/packages/core/test/lib/utils/eventbuilder.test.ts b/packages/core/test/lib/utils/eventbuilder.test.ts index ea0d2bb6aae3..5a2fcc003419 100644 --- a/packages/core/test/lib/utils/eventbuilder.test.ts +++ b/packages/core/test/lib/utils/eventbuilder.test.ts @@ -334,6 +334,15 @@ describe('exceptionFromError', () => { ['an array', [1, 'two'], '[1,"two"]'], ['a circular object', circular, '[object Object]'], ['a function', function namedFn() {}, '[Function: namedFn]'], + ['an error', new Error('inner'), '[object Error]'], + ['a map', new Map([['code', 42]]), '[object Map]'], + [ + 'a class instance', + new (class CustomMessage { + code = 42; + })(), + '{"code":42}', + ], ])('turns %s into a string', (_, message, expected) => { const error = new Error('placeholder'); (error as { message: unknown }).message = message;