From ed6bd6316080ba1a0e59a8245102e0136f784fff Mon Sep 17 00:00:00 2001 From: Santusht kotai <115890693+santusht06@users.noreply.github.com> Date: Thu, 27 Aug 2026 15:21:39 +0530 Subject: [PATCH] process: expose enhanced stack trace to uncaughtException handlers When an EventEmitter instance emits an unhandled 'error' event, Node attaches an internal stack enhancer (`kEnhanceStackBeforeInspector`) capturing the call site of the `emit('error', ...)` invocation. However, `createOnGlobalUncaughtException()` was dispatching the error to `uncaughtExceptionMonitor` and `uncaughtException` listeners before running `fatalExceptionStackEnhancers.beforeInspector(er)`. As a result, custom uncaught exception handlers and monitoring libraries did not receive the enhanced call site on `err.stack`. This commit runs `fatalExceptionStackEnhancers.beforeInspector(er)` before emitting `uncaughtExceptionMonitor` and `uncaughtException`, ensuring the full emitter call site is accessible to handlers. Fixes: https://github.com/nodejs/node/issues/55838 Signed-off-by: Santusht kotai <115890693+santusht06@users.noreply.github.com> --- lib/internal/process/execution.js | 5 ++ ...ocess-uncaught-exception-enhanced-stack.js | 47 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 test/parallel/test-process-uncaught-exception-enhanced-stack.js diff --git a/lib/internal/process/execution.js b/lib/internal/process/execution.js index 46cf9f95407a..c939e3842937 100644 --- a/lib/internal/process/execution.js +++ b/lib/internal/process/execution.js @@ -17,6 +17,7 @@ const { ERR_INVALID_ARG_TYPE, ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET, }, + fatalExceptionStackEnhancers, } = require('internal/errors'); const { validateFunction } = require('internal/validators'); const { pathToFileURL } = require('internal/url'); @@ -174,6 +175,10 @@ function createOnGlobalUncaughtException() { // call that threw and was never cleared. So clear it now. clearDefaultTriggerAsyncId(); + if (er != null && typeof er === 'object') { + fatalExceptionStackEnhancers.beforeInspector(er); + } + const type = fromPromise ? 'unhandledRejection' : 'uncaughtException'; process.emit('uncaughtExceptionMonitor', er, type); // Primary callback (e.g., domain) has priority and always handles the exception diff --git a/test/parallel/test-process-uncaught-exception-enhanced-stack.js b/test/parallel/test-process-uncaught-exception-enhanced-stack.js new file mode 100644 index 000000000000..c45ba9cb0a78 --- /dev/null +++ b/test/parallel/test-process-uncaught-exception-enhanced-stack.js @@ -0,0 +1,47 @@ +'use strict'; +const common = require('../common'); +const assert = require('node:assert'); +const EventEmitter = require('node:events'); + +class CustomEmitter extends EventEmitter {} + +const ee = new EventEmitter(); +const customEE = new CustomEmitter(); + +let monitorCount = 0; +let uncaughtCount = 0; + +process.on('uncaughtExceptionMonitor', common.mustCall((err, origin) => { + assert.strictEqual(origin, 'uncaughtException'); + monitorCount++; + if (monitorCount === 1) { + assert.match(err.stack, /Emitted 'error' event at:/); + assert.match(err.stack, /at emitPlainError/); + } else if (monitorCount === 2) { + assert.match(err.stack, /Emitted 'error' event on CustomEmitter instance at:/); + assert.match(err.stack, /at emitCustomClassError/); + } +}, 2)); + +process.on('uncaughtException', common.mustCall((err, origin) => { + assert.strictEqual(origin, 'uncaughtException'); + uncaughtCount++; + if (uncaughtCount === 1) { + assert.match(err.stack, /Emitted 'error' event at:/); + assert.match(err.stack, /at emitPlainError/); + process.nextTick(emitCustomClassError); + } else if (uncaughtCount === 2) { + assert.match(err.stack, /Emitted 'error' event on CustomEmitter instance at:/); + assert.match(err.stack, /at emitCustomClassError/); + } +}, 2)); + +function emitPlainError() { + ee.emit('error', new Error('plain error')); +} + +function emitCustomClassError() { + customEE.emit('error', new Error('custom class error')); +} + +emitPlainError();