diff --git a/extensions/ql-vscode/src/extension.ts b/extensions/ql-vscode/src/extension.ts index fce563b9375..81f248957f7 100644 --- a/extensions/ql-vscode/src/extension.ts +++ b/extensions/ql-vscode/src/extension.ts @@ -236,6 +236,7 @@ export async function activate( const distributionConfigListener = new DistributionConfigListener(); await initializeLogging(ctx); await initializeTelemetry(extension, ctx); + addUnhandledRejectionListener(); install(); const codelensProvider = new QuickEvalCodeLensProvider(); @@ -1529,6 +1530,35 @@ async function activateWithInstalledDistribution( }; } +function addUnhandledRejectionListener() { + const handler = (error: unknown) => { + const message = redactableError( + asError(error), + )`Unhandled error: ${getErrorMessage(error)}`; + // Add a catch so that showAndLogExceptionWithTelemetry fails, we avoid + // triggering "unhandledRejection" and avoid an infinite loop + showAndLogExceptionWithTelemetry(message).catch( + (telemetryError: unknown) => { + void extLogger.log( + `Failed to send error telemetry: ${getErrorMessage(telemetryError)}`, + ); + void extLogger.log(message.fullMessage); + }, + ); + }; + + // "uncaughtException" will trigger whenever an exception reaches the top level. + // This covers extension initialization and any code within a `setTimeout`. + // Notably this does not include exceptions thrown when executing commands, + // because `commandRunner` wraps the command body and handles errors. + process.addListener("uncaughtException", handler); + + // "unhandledRejection" will trigger whenever any promise is rejected and it is + // not handled by a "catch" somewhere in the promise chain. This includes when + // a promise is used with the "void" operator. + process.addListener("unhandledRejection", handler); +} + async function createQueryServer( qlConfigurationListener: QueryServerConfigListener, cliServer: CodeQLCliServer,