Skip to content

fix(core): Deduplicate native HTTP breadcrumbs#6132

Open
antonis wants to merge 7 commits into
mainfrom
antonis/fix-duplicate-http-breadcrumbs
Open

fix(core): Deduplicate native HTTP breadcrumbs#6132
antonis wants to merge 7 commits into
mainfrom
antonis/fix-duplicate-http-breadcrumbs

Conversation

@antonis
Copy link
Copy Markdown
Contributor

@antonis antonis commented May 11, 2026

📢 Type of change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring

📜 Description

When JS makes an HTTP request in React Native, breadcrumbs are created independently by both the JS SDK (xhr category) and the native SDK (http category), because RN's networking layer (fetch/XHR) is implemented via native APIs (NSURLSession on iOS, OkHttp on Android). This results in duplicate breadcrumbs for every HTTP request.

This PR adds deduplication logic in deviceContextIntegration.processEvent that filters out native http breadcrumbs when a matching JS xhr/fetch breadcrumb exists (same method, URL, status code, and timestamp within 2s tolerance).

Key design choices:

  • Keeps the JS breadcrumb, drops the native one — the JS breadcrumb goes through beforeBreadcrumb, giving users filtering control
  • Consumed-match tracking — each JS breadcrumb can only absorb one native duplicate, preventing false positives with consecutive identical requests
  • Only targets native HTTP breadcrumbs — non-HTTP native breadcrumbs (ui.click, etc.) are untouched
  • Native-only HTTP requests preserved — requests from native code (ObjC/Swift/Java) with no JS counterpart are kept
  • Handles string/number status_code mismatch — uses Number() coercion for comparison

Also updates the FIXME comment in breadcrumbs.ts to reflect that deduplication now happens in deviceContextIntegration.

💡 Motivation and Context

Closes #3045

Users see duplicate HTTP breadcrumbs (2x per request) which crowd out meaningful breadcrumbs and cannot be filtered via beforeBreadcrumb since native breadcrumbs bypass that callback.

The sentry-cocoa side of this (cocoa producing its own internal duplicates, #2971) was fixed in sentry-cocoa 8.8.0. The remaining duplication is the JS vs native layer, which this PR addresses.

💚 How did you test it?

  • Added 8 unit tests covering: basic dedup, no-match preservation, non-HTTP breadcrumb preservation, timestamp tolerance boundary, multiple identical requests, string/number status_code coercion, empty JS breadcrumbs, and fetch category support
  • All existing tests pass unmodified
  • Build passes (yarn build)
  • Lint passes (yarn lint)

📝 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

…fetch breadcrumbs

Closes #3045

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 11, 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(core): Deduplicate native HTTP breadcrumbs by antonis in #6132
  • Filter ExceptionsManager.reportException duplicates in app-start init by alwx in #6145
  • chore(deps): update JavaScript SDK to v10.53.1 by github-actions in #6139
  • feat(core): Enable autoInjectSentryLabel by default in Metro config by antonis in #6141
  • feat(core): Respect Mask boundaries when reading sentry-label by antonis in #6142
  • fix(android): Handle boolean values in JSON options converter by antonis in #6130
  • Multi-instance <TimeToInitialDisplay> / <TimeToFullDisplay> coordination; a multi-signal TTID/TTFD system by alwx in #6090
  • chore(deps): update Bundler Plugins to v5.3.0 by github-actions in #6138
  • chore: Merge 8.11.1 back to main by antonis in #6135
  • chore: Update warning regarding iOS crash in sentry-cocoa 9.12.0 by antonis in #6136
  • chore(deps): update CLI to v3.4.2 by github-actions in #6129
  • chore(deps): bump getsentry/craft/.github/workflows/changelog-preview.yml from 2.26.2 to 2.26.3 by dependabot in #6126
  • chore(deps): bump getsentry/craft from 2.26.2 to 2.26.3 by dependabot in #6127
  • chore(deps): bump github/codeql-action from 4.35.3 to 4.35.4 by dependabot in #6128
  • feat(core): Extract text from children of touched components for breadcrumb labels by antonis in #6106
  • chore(deps): bump @babel/plugin-transform-modules-systemjs from 7.25.0 to 7.29.4 by dependabot in #6124
  • chore(deps): bump fast-uri from 3.0.1 to 3.1.2 by dependabot in #6121
  • chore(deps): bump fast-xml-builder from 1.1.5 to 1.2.0 by dependabot in #6120
  • chore(deps): bump socks to ^2.8.8 to fix ip-address vulnerability by antonis in #6117
  • chore(deps): bump uuid to ^13.0.1 to fix buffer bounds check vulnerability by antonis in #6118
  • test(replay): Add passthrough tests for device-state replay breadcrumbs by antonis in #6115
  • chore(deps): update JavaScript SDK to v10.52.0 by github-actions in #6108
  • chore(deps): bump basic-ftp from 5.3.0 to 5.3.1 by dependabot in #6111

🤖 This preview updates automatically when you update the PR.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit d52d0ef. Configure here.

@antonis antonis marked this pull request as ready for review May 11, 2026 13:31
Comment thread packages/core/src/js/integrations/devicecontext.ts Outdated
Network errors, aborted requests, and CORS failures produce breadcrumbs
without a status_code. Treat both sides being null/undefined as a match.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread packages/core/src/js/integrations/devicecontext.ts
Previously only string timestamps were parsed; numeric timestamps
(seconds since epoch) were silently dropped.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@lucas-zimerman
Copy link
Copy Markdown
Collaborator

Nice fix!

}

const sameMethod = nativeBreadcrumb.data?.method === jsBreadcrumb.data?.method;
const sameUrl = nativeBreadcrumb.data?.url === jsBreadcrumb.data?.url;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What do you think of using something like endsWith here? Usually the start of the backend url will always match, only the end/center will vary. That could skip checking the initial URL.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Although if almost all the time this function is called is a match, then it's a bit pointless to check for it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, and having a looser check with endsWith might risk false positives

@lucas-zimerman
Copy link
Copy Markdown
Collaborator

Question: Can't we simply disable the auto capture for http request on the native side?
Also thinking if on this case, we cant automatically tag the breadcrumb as manually captured by the end user or not.

@antonis
Copy link
Copy Markdown
Contributor Author

antonis commented May 13, 2026

Question: Can't we simply disable the auto capture for http request on the native side? Also thinking if on this case, we cant automatically tag the breadcrumb as manually captured by the end user or not.

@lucas-zimerman Good question 👍 I considered it as an approach (option 1 here) and I agree that is is a cleaner architecturally. In reality disabling didn't work since we don't know if a request came from JS or from native code. The solution is flagging the JS requests (e.g. extra flag, see option 3) and filtering out on the native side resulting in a more complicated solution. Approach 4 seemed simpler to implement but I'm open to discuss the alternatives.

@antonis antonis requested a review from lucas-zimerman May 13, 2026 09:49
@antonis antonis added the ready-to-merge Triggers the full CI test suite label May 15, 2026
@github-actions
Copy link
Copy Markdown
Contributor

iOS (legacy) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 3850.49 ms 1223.47 ms -2627.02 ms
Size 5.15 MiB 6.68 MiB 1.53 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
d038a14+dirty 3845.71 ms 1228.11 ms -2617.59 ms
5569641+dirty 3839.22 ms 1231.30 ms -2607.91 ms
5a21b51+dirty 3823.11 ms 1214.46 ms -2608.65 ms
4b87b12+dirty 1212.90 ms 1222.09 ms 9.19 ms
f3215d3+dirty 3842.73 ms 1219.33 ms -2623.40 ms
7d6fd3a+dirty 1223.29 ms 1229.57 ms 6.28 ms
3d377b5+dirty 1218.48 ms 1219.51 ms 1.03 ms
23598c3+dirty 1207.00 ms 1209.90 ms 2.90 ms
4953e94+dirty 1212.06 ms 1214.83 ms 2.77 ms
04207c4+dirty 1191.27 ms 1189.78 ms -1.48 ms

App size

Revision Plain With Sentry Diff
d038a14+dirty 5.15 MiB 6.67 MiB 1.51 MiB
5569641+dirty 5.15 MiB 6.67 MiB 1.51 MiB
5a21b51+dirty 5.15 MiB 6.67 MiB 1.51 MiB
4b87b12+dirty 3.38 MiB 4.77 MiB 1.39 MiB
f3215d3+dirty 5.15 MiB 6.67 MiB 1.52 MiB
7d6fd3a+dirty 3.38 MiB 4.77 MiB 1.39 MiB
3d377b5+dirty 3.38 MiB 4.76 MiB 1.38 MiB
23598c3+dirty 3.38 MiB 4.80 MiB 1.42 MiB
4953e94+dirty 3.38 MiB 4.73 MiB 1.35 MiB
04207c4+dirty 3.38 MiB 4.76 MiB 1.38 MiB

@sentry
Copy link
Copy Markdown

sentry Bot commented May 15, 2026

📲 Install Builds

Android

🔗 App Name App ID Version Configuration
Sentry RN io.sentry.reactnative.sample 8.11.1 (88) Release

⚙️ sentry-react-native Build Distribution Settings

@github-actions
Copy link
Copy Markdown
Contributor

Android (legacy) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 521.48 ms 555.63 ms 34.15 ms
Size 48.30 MiB 53.54 MiB 5.24 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
3d377b5+dirty 406.18 ms 453.52 ms 47.34 ms
8929511+dirty 405.33 ms 452.16 ms 46.83 ms
100ce80+dirty 463.66 ms 539.56 ms 75.90 ms
3817909+dirty 406.67 ms 416.58 ms 9.91 ms
3b6e9f9+dirty 442.70 ms 486.44 ms 43.74 ms
4e0ba9c+dirty 452.84 ms 473.36 ms 20.52 ms
5fe1c6c+dirty 401.62 ms 445.28 ms 43.66 ms
44c8b3f+dirty 414.20 ms 457.28 ms 43.08 ms
890d145+dirty 504.54 ms 491.55 ms -12.99 ms
3ce5254+dirty 410.57 ms 448.48 ms 37.91 ms

App size

Revision Plain With Sentry Diff
3d377b5+dirty 43.75 MiB 48.14 MiB 4.39 MiB
8929511+dirty 43.75 MiB 48.16 MiB 4.41 MiB
100ce80+dirty 48.30 MiB 53.46 MiB 5.15 MiB
3817909+dirty 43.75 MiB 48.08 MiB 4.33 MiB
3b6e9f9+dirty 48.30 MiB 53.54 MiB 5.23 MiB
4e0ba9c+dirty 48.30 MiB 53.49 MiB 5.19 MiB
5fe1c6c+dirty 43.75 MiB 48.14 MiB 4.39 MiB
44c8b3f+dirty 48.30 MiB 53.46 MiB 5.15 MiB
890d145+dirty 43.75 MiB 48.14 MiB 4.39 MiB
3ce5254+dirty 43.75 MiB 48.12 MiB 4.37 MiB

@github-actions
Copy link
Copy Markdown
Contributor

iOS (new) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 3828.33 ms 1217.13 ms -2611.20 ms
Size 5.15 MiB 6.68 MiB 1.53 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
d038a14+dirty 3831.11 ms 1216.30 ms -2614.81 ms
5569641+dirty 3824.35 ms 1210.78 ms -2613.57 ms
5a21b51+dirty 3837.87 ms 1223.47 ms -2614.40 ms
4b87b12+dirty 1199.49 ms 1199.78 ms 0.29 ms
f3215d3+dirty 3846.08 ms 1231.85 ms -2614.23 ms
7d6fd3a+dirty 1210.89 ms 1217.63 ms 6.74 ms
3d377b5+dirty 1201.55 ms 1201.80 ms 0.25 ms
23598c3+dirty 1223.59 ms 1229.13 ms 5.53 ms
4953e94+dirty 1217.41 ms 1223.53 ms 6.12 ms
04207c4+dirty 1228.55 ms 1226.04 ms -2.51 ms

App size

Revision Plain With Sentry Diff
d038a14+dirty 5.15 MiB 6.67 MiB 1.51 MiB
5569641+dirty 5.15 MiB 6.67 MiB 1.51 MiB
5a21b51+dirty 5.15 MiB 6.67 MiB 1.51 MiB
4b87b12+dirty 3.38 MiB 4.77 MiB 1.39 MiB
f3215d3+dirty 5.15 MiB 6.67 MiB 1.52 MiB
7d6fd3a+dirty 3.38 MiB 4.77 MiB 1.39 MiB
3d377b5+dirty 3.38 MiB 4.76 MiB 1.38 MiB
23598c3+dirty 3.38 MiB 4.80 MiB 1.42 MiB
4953e94+dirty 3.38 MiB 4.73 MiB 1.35 MiB
04207c4+dirty 3.38 MiB 4.76 MiB 1.38 MiB

@github-actions
Copy link
Copy Markdown
Contributor

Android (new) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 409.00 ms 427.40 ms 18.40 ms
Size 48.30 MiB 53.54 MiB 5.24 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
a736b76+dirty 405.78 ms 458.74 ms 52.96 ms
890d145+dirty 486.42 ms 514.85 ms 28.43 ms
5fe1c6c+dirty 365.84 ms 408.62 ms 42.78 ms
44c8b3f+dirty 492.13 ms 563.47 ms 71.34 ms
04207c4+dirty 395.40 ms 456.55 ms 61.15 ms
3b6e9f9+dirty 442.39 ms 486.44 ms 44.05 ms
4e0ba9c+dirty 421.39 ms 455.80 ms 34.41 ms
3817909+dirty 357.52 ms 391.52 ms 34.00 ms
bc0d8cf+dirty 407.66 ms 461.35 ms 53.69 ms
5569641+dirty 465.92 ms 532.22 ms 66.30 ms

App size

Revision Plain With Sentry Diff
a736b76+dirty 48.30 MiB 53.48 MiB 5.18 MiB
890d145+dirty 43.94 MiB 49.00 MiB 5.06 MiB
5fe1c6c+dirty 43.94 MiB 49.00 MiB 5.06 MiB
44c8b3f+dirty 48.30 MiB 53.46 MiB 5.15 MiB
04207c4+dirty 43.94 MiB 48.98 MiB 5.04 MiB
3b6e9f9+dirty 48.30 MiB 53.54 MiB 5.23 MiB
4e0ba9c+dirty 48.30 MiB 53.49 MiB 5.19 MiB
3817909+dirty 43.94 MiB 48.94 MiB 5.00 MiB
bc0d8cf+dirty 48.30 MiB 53.48 MiB 5.18 MiB
5569641+dirty 48.30 MiB 53.48 MiB 5.18 MiB

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

Labels

ready-to-merge Triggers the full CI test suite

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Duplicate http / xhr breadcrumbs

2 participants