Skip to content

Fix(Replay): dont record ignored/dropped errors#5885

Open
lucas-zimerman wants to merge 7 commits intomainfrom
lz/ignore-replay
Open

Fix(Replay): dont record ignored/dropped errors#5885
lucas-zimerman wants to merge 7 commits intomainfrom
lz/ignore-replay

Conversation

@lucas-zimerman
Copy link
Collaborator

@lucas-zimerman lucas-zimerman commented Mar 25, 2026

📢 Type of change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring

📜 Description

Users wanted to only track errors, but the way mobileReplay was set, it triggered replay captured even to dropped errors, since it was doing this on the middle of the event processing workflow, which could lead to dropped events before sending it.

💡 Motivation and Context

The fix is a hook into beforeSend, since it's the last stop before sending an event.
Ideally we shold use something like client.on('beforeBeforeSend... but the client hook was syncronous, which blocks the usage of Native APIs to fetch the replay data to fill into the event's context.

This way, when users set to only capture errors, Replay will not send recording data when the only captured events were dropped by IgnoredError/eventProcessor or beforeSend.

💚 How did you test it?

On a device, sample app.

📝 Checklist

  • I added tests to verify changes
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • All tests passing
  • No breaking changes

🔮 Next steps

Close #4908

@github-actions
Copy link
Contributor

github-actions bot commented Mar 25, 2026

Semver Impact of This PR

None (no version bump detected)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


  • Fix(Replay): dont record ignored/dropped errors by lucas-zimerman in #5885
  • fix(core): Fix stale deprecation references by antonis in #5881
  • chore(build): Rename version-bump.js to bump-version.js by antonis in #5883
  • chore(deps): bump activesupport from 7.0.8.6 to 7.2.3.1 in /samples/react-native-macos by dependabot in #5870
  • chore(deps): bump activesupport from 6.1.7.10 to 7.2.3.1 in /samples/react-native by dependabot in #5869
  • chore(deps): bump yauzl to ^3.2.1 by antonis in #5855
  • chore(deps): bump appium from 2.4.1 to 3.2.2 by antonis in #5856
  • fix(ios): Guard replay postInit behind runtime session replay check by antonis in #5858
  • Add better needs_web check to CI by alwx in #5863
  • chore(deps): bump fast-xml-parser to ^5.5.7 by antonis in #5854
  • CI: detect-changes workflow to only check the affected components on the CI side by alwx in #5843
  • chore(deps): bump getsentry/craft/.github/workflows/changelog-preview.yml from 2.24.1 to 2.25.0 by dependabot in #5861
  • chore(deps): bump getsentry/craft from 2.24.1 to 2.25.0 by dependabot in #5862
  • chore(deps): bump github/codeql-action from 4.32.6 to 4.34.1 by dependabot in #5860
  • chore(deps): update JavaScript SDK to v10.45.0 by github-actions in #5848
  • chore(deps): bump flatted from 3.4.1 to 3.4.2 by dependabot in #5853
  • chore(deps): update Cocoa SDK to v9.8.0 by github-actions in #5847
  • fix(tracing): Guard getNewScreenTimeToDisplay behind enableTimeToInitialDisplay by antonis in #5849
  • chore(deps): bump json from 2.16.0 to 2.17.1.2 in /performance-tests by dependabot in #5844
  • chore(docs): Add changelog entry for duplicated breadcrumbs fix by antonis in #5851
  • fix(tracing): Unsubscribe spanEnd listeners after they fire to prevent accumulation by antonis in #5840
  • fix(android): Properly remove duplicated breadcrumbs by vovkasm in #5841
  • fix(tracing): Skip native frames and stall tracking for unsampled spans by antonis in #5842

🤖 This preview updates automatically when you update the PR.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 25, 2026

Fails
🚫 Pull request is not ready for merge, please add the "ready-to-merge" label to the pull request

Generated by 🚫 dangerJS against a9067a0

@lucas-zimerman lucas-zimerman marked this pull request as ready for review March 25, 2026 14:58
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Replay bridge errors can drop events
    • Added try-catch around processEvent in beforeSend to ensure events are sent without replay data when native bridge calls fail, rather than being dropped.

Create PR

Or push these changes by commenting:

@cursor push 6824446734
Preview (6824446734)
diff --git a/packages/core/src/js/replay/mobilereplay.ts b/packages/core/src/js/replay/mobilereplay.ts
--- a/packages/core/src/js/replay/mobilereplay.ts
+++ b/packages/core/src/js/replay/mobilereplay.ts
@@ -308,15 +308,21 @@
     const clientOptions = client.getOptions();
     const originalBeforeSend = clientOptions.beforeSend;
     clientOptions.beforeSend = async (event: ErrorEvent, hint: EventHint): Promise<ErrorEvent | null> => {
+      let eventToProcess = event;
       if (originalBeforeSend) {
         const result = await originalBeforeSend(event, hint);
         if (result === null) {
           // Event was dropped by user's beforeSend, don't capture replay
           return null;
         }
-        return processEvent(result, hint);
+        eventToProcess = result;
       }
-      return processEvent(event, hint);
+      try {
+        return await processEvent(eventToProcess, hint);
+      } catch (error) {
+        debug.error(`[Sentry] ${MOBILE_REPLAY_INTEGRATION_NAME} failed to process event, sending without replay data`, error);
+        return eventToProcess;
+      }
     };
   }
 

diff --git a/packages/core/test/replay/mobilereplay.test.ts b/packages/core/test/replay/mobilereplay.test.ts
--- a/packages/core/test/replay/mobilereplay.test.ts
+++ b/packages/core/test/replay/mobilereplay.test.ts
@@ -143,6 +143,57 @@
       expect(mockCaptureReplay).not.toHaveBeenCalled();
       expect(result?.contexts?.replay).toBeUndefined();
     });
+
+    it('should return event unchanged when native bridge call fails', async () => {
+      mockCaptureReplay.mockRejectedValue(new Error('Native bridge error'));
+
+      const integration = mobileReplayIntegration();
+      integration.setup?.(mockClient);
+
+      const event = {
+        event_id: 'test-event-id',
+        exception: {
+          values: [{ type: 'Error', value: 'Test error' }],
+        },
+      } as ErrorEvent;
+      const hint: EventHint = {};
+
+      const result = await clientOptions.beforeSend?.(event, hint);
+
+      expect(result).toBeDefined();
+      expect(result).toBe(event);
+      expect(result?.contexts?.replay).toBeUndefined();
+    });
+
+    it('should return modified event from user beforeSend when native bridge fails', async () => {
+      mockCaptureReplay.mockRejectedValue(new Error('Native bridge error'));
+
+      const userBeforeSend = jest
+        .fn<(event: ErrorEvent, hint: EventHint) => ErrorEvent>()
+        .mockImplementation(event => ({
+          ...event,
+          tags: { modified: 'true' },
+        }));
+      clientOptions.beforeSend = userBeforeSend;
+
+      const integration = mobileReplayIntegration();
+      integration.setup?.(mockClient);
+
+      const event = {
+        event_id: 'test-event-id',
+        exception: {
+          values: [{ type: 'Error', value: 'Test error' }],
+        },
+      } as ErrorEvent;
+      const hint: EventHint = {};
+
+      const result = await clientOptions.beforeSend?.(event, hint);
+
+      expect(result).toBeDefined();
+      expect(userBeforeSend).toHaveBeenCalledWith(event, hint);
+      expect(result?.tags).toEqual({ modified: 'true' });
+      expect(result?.contexts?.replay).toBeUndefined();
+    });
   });
 
   describe('beforeErrorSampling', () => {

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

return processEvent(result, hint);
}
return processEvent(event, hint);
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replay bridge errors can drop events

High Severity

beforeSend awaits processEvent without guarding failures. If replay bridge calls fail, the wrapped beforeSend rejects and the SDK drops the original event instead of sending it without replay data.

Additional Locations (1)
Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sentry Replays counting muted Errors or something off

1 participant