Repository: getsentry/sentry
Pull request: fix: Suppress unexpected Type Error in capturing RouteError
Wraps unreproducible Type Error (missing setter) in Error.message property in a try-catch. Type
Error is unreproducible, and when thrown causes intital error to be ignored and the newer and less
informative Type Error to be captured by Sentry instead. Currently, a minority of users are
affected and certain browser and/or browser extension incompatibility is suspected.

Suppression should help in the discovery of the actual errors causing the RouteError and perhaps
uncover the root cause of the Type Error.

Fixes GH-16314
Fixes Sentry-JAVASCRIPT-129Q

Problem statement:
Capturing RouteError fails unexpectedly
Sometimes when capturing a RouteError, writing to the `message` property of JavaScript's `Error` object causes a `Type Error: setting getter-only property "message"`.

The new Type Error exception thrown is then logged into Sentry, causing the previous RouteError to be discarded.

While instances are captured in Sentry, these errors are not reproducible. The `message` property should be writable, it is unclear why these errors are being thrown in this scenario. @billyvg suspects browser or browser extension incompatibility.

Hints from issue discussion:

Commit message: Capture error emitted from try-catch

Patch to review:
diff --git a/src/sentry/static/sentry/app/views/routeError.jsx b/src/sentry/static/sentry/app/views/routeError.jsx
index 12a4186a1f3349..1a532330b36e85 100644
--- a/src/sentry/static/sentry/app/views/routeError.jsx
+++ b/src/sentry/static/sentry/app/views/routeError.jsx
@@ -35,17 +35,36 @@ class RouteError extends React.Component {
     }
 
     const route = getRouteStringFromRoutes(routes);
+    const enrichScopeContext = scope => {
+      scope.setExtra('route', route);
+      scope.setExtra('orgFeatures', (organization && organization.features) || []);
+      scope.setExtra('orgAccess', (organization && organization.access) || []);
+      scope.setExtra('projectFeatures', (project && project.features) || []);
+      return scope;
+    };
+
     if (route) {
-      error.message = `${error.message}: ${route}`;
+      /**
+       * Unexpectedly, error.message would sometimes not have a setter property, causing another exception to be thrown,
+       * and losing the original error in the process. Wrapping the mutation in a try-catch in an attempt to preserve
+       * the original error for logging.
+       * See https://github.com/getsentry/sentry/issues/16314 for more details.
+       */
+      try {
+        error.message = `${error.message}: ${route}`;
+      } catch (e) {
+        Sentry.withScope(scope => {
+          scope.setFingerprint(['route-error', 'error-message-mutation-error']);
+          enrichScopeContext(scope);
+          Sentry.captureException(e);
+        });
+      }
     }
     // TODO(dcramer): show something in addition to embed (that contains it?)
     // throw this in a timeout so if it errors we dont fall over
     this._timeout = window.setTimeout(() => {
       Sentry.withScope(scope => {
-        scope.setExtra('route', route);
-        scope.setExtra('orgFeatures', (organization && organization.features) || []);
-        scope.setExtra('orgAccess', (organization && organization.access) || []);
-        scope.setExtra('projectFeatures', (project && project.features) || []);
+        enrichScopeContext(scope);
         Sentry.captureException(error);
       });
 


Code review comments:
diff_hunk: @@ -35,17 +35,36 @@ class RouteError extends React.Component {
     }
 
     const route = getRouteStringFromRoutes(routes);
+    const enrichScopeContext = scope => {
+      scope.setExtra('route', route);
+      scope.setExtra('orgFeatures', (organization && organization.features) || []);
+      scope.setExtra('orgAccess', (organization && organization.access) || []);
+      scope.setExtra('projectFeatures', (project && project.features) || []);
+      return scope;
+    };
+
     if (route) {
-      error.message = `${error.message}: ${route}`;
+      /**
+       * Unexpectedly, error.message would sometimes not have a setter property, causing another exception to be thrown,
+       * and losing the original error in the process. Wrapping the mutation in a try-catch in an attempt to preserve
+       * the original error for logging.
+       * See https://github.com/getsentry/sentry/issues/16314 for more details.
+       */
+      try {
+        error.message = `${error.message}: ${route}`;
+      } catch (e) {
+        Sentry.withScope(scope => {
+          scope.setFingerprint(['route-error', 'error-message-mutation-error']);
path: src/sentry/static/sentry/app/views/routeError.jsx
text: @user1:
Let's remove the fingerprint

@author:
Done