diff --git a/src/bun.js.zig b/src/bun.js.zig index fb59390ea0..2a71f017ca 100644 --- a/src/bun.js.zig +++ b/src/bun.js.zig @@ -323,7 +323,7 @@ pub const Run = struct { var printed_sourcemap_warning_and_version = false; if (vm.loadEntryPoint(this.entry_path)) |promise| { - if (promise.status(vm.global.vm()) == .rejected) { + if (promise.status(vm.global.vm()) == .rejected and !promise.isHandled(vm.global.vm())) { const handled = vm.uncaughtException(vm.global, promise.result(vm.global.vm()), true); promise.setHandled(vm.global.vm()); diff --git a/src/bun.js/VirtualMachine.zig b/src/bun.js/VirtualMachine.zig index bb91dc2be0..ea9028596a 100644 --- a/src/bun.js/VirtualMachine.zig +++ b/src/bun.js/VirtualMachine.zig @@ -569,6 +569,15 @@ pub fn unhandledRejection(this: *jsc.VirtualMachine, globalObject: *JSGlobalObje switch (this.unhandledRejectionsMode()) { .bun => { if (Bun__handleUnhandledRejection(globalObject, reason, promise) > 0) return; + // Check if the exception is handled by capture callback + const wrapped_reason = wrapUnhandledRejectionErrorForUncaughtException(globalObject, reason); + if (this.uncaughtException(globalObject, wrapped_reason, true)) { + // Mark the promise as handled to prevent double handling + if (promise.asAnyPromise()) |internal_promise| { + internal_promise.setHandled(this.global.vm()); + } + return; + } // continue to default handler }, .none => { @@ -660,15 +669,17 @@ pub fn uncaughtException(this: *jsc.VirtualMachine, globalObject: *JSGlobalObjec bun.api.node.process.exit(globalObject, 7); @panic("Uncaught exception while handling uncaught exception"); } - if (this.exit_on_uncaught_exception) { - this.runErrorHandler(err, null); - bun.api.node.process.exit(globalObject, 1); - @panic("made it past Bun__Process__exit"); - } this.is_handling_uncaught_exception = true; defer this.is_handling_uncaught_exception = false; - const handled = Bun__handleUncaughtException(globalObject, err.toError() orelse err, if (is_rejection) 1 else 0) > 0; + const handled_int = Bun__handleUncaughtException(globalObject, err.toError() orelse err, if (is_rejection) 1 else 0); + const handled = handled_int > 0; if (!handled) { + // If the exception was not handled, check if we should exit + if (this.exit_on_uncaught_exception) { + this.runErrorHandler(err, null); + bun.api.node.process.exit(globalObject, 1); + @panic("made it past Bun__Process__exit"); + } // TODO maybe we want a separate code path for uncaught exceptions this.unhandled_error_counter += 1; this.exit_handler.exit_code = 1; diff --git a/src/bun.js/bindings/BunProcess.cpp b/src/bun.js/bindings/BunProcess.cpp index b243763dcd..acd46b0cb4 100644 --- a/src/bun.js/bindings/BunProcess.cpp +++ b/src/bun.js/bindings/BunProcess.cpp @@ -1109,7 +1109,10 @@ extern "C" int Bun__handleUncaughtException(JSC::JSGlobalObject* lexicalGlobalOb auto capture = process->getUncaughtExceptionCaptureCallback(); if (!capture.isEmpty() && !capture.isUndefinedOrNull()) { auto scope = DECLARE_CATCH_SCOPE(vm); - (void)call(lexicalGlobalObject, capture, args, "uncaughtExceptionCaptureCallback"_s); + // setUncaughtExceptionCaptureCallback expects only the exception, not the event name + MarkedArgumentBuffer captureArgs; + captureArgs.append(exception); + (void)call(lexicalGlobalObject, capture, captureArgs, "uncaughtExceptionCaptureCallback"_s); if (auto ex = scope.exception()) { scope.clearException(); // if an exception is thrown in the uncaughtException handler, we abort