fix(ios): Fix screenshot and view hierarchy capture failing on iOS#6491
Conversation
Semver Impact of This PR⚪ None (no version bump detected) 📋 Changelog PreviewThis is how your changes will appear in the changelog.
🤖 This preview updates automatically when you update the PR. |
📲 Install BuildsAndroid
|
iOS (legacy) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| b9bebee+dirty | 3850.15 ms | 1227.51 ms | -2622.64 ms |
| 3d377b5+dirty | 1218.48 ms | 1219.51 ms | 1.03 ms |
| 1a2e7e0+dirty | 3842.49 ms | 1220.04 ms | -2622.45 ms |
| e5bb5f6+dirty | 3826.14 ms | 1212.24 ms | -2613.90 ms |
| 6acdf1d+dirty | 3844.33 ms | 1212.96 ms | -2631.38 ms |
| 41d6254+dirty | 3845.71 ms | 1224.51 ms | -2621.20 ms |
| 9210ae6+dirty | 3815.93 ms | 1214.14 ms | -2601.79 ms |
| c004dae+dirty | 3850.32 ms | 1227.79 ms | -2622.53 ms |
| 4e0b819+dirty | 3839.05 ms | 1210.75 ms | -2628.30 ms |
| 0b5120f+dirty | 3838.39 ms | 1232.91 ms | -2605.48 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| b9bebee+dirty | 5.15 MiB | 6.68 MiB | 1.53 MiB |
| 3d377b5+dirty | 3.38 MiB | 4.76 MiB | 1.38 MiB |
| 1a2e7e0+dirty | 4.98 MiB | 6.46 MiB | 1.49 MiB |
| e5bb5f6+dirty | 4.98 MiB | 6.51 MiB | 1.53 MiB |
| 6acdf1d+dirty | 4.98 MiB | 6.51 MiB | 1.53 MiB |
| 41d6254+dirty | 5.15 MiB | 6.70 MiB | 1.54 MiB |
| 9210ae6+dirty | 5.15 MiB | 6.68 MiB | 1.53 MiB |
| c004dae+dirty | 5.15 MiB | 6.67 MiB | 1.51 MiB |
| 4e0b819+dirty | 4.98 MiB | 6.46 MiB | 1.49 MiB |
| 0b5120f+dirty | 5.15 MiB | 6.68 MiB | 1.53 MiB |
Previous results on branch: fix/ios-feedback-screenshot-capture
Startup times
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 5d33fdc+dirty | 3840.27 ms | 1207.50 ms | -2632.77 ms |
| 5d33fdc+dirty | 3863.38 ms | 1237.41 ms | -2625.96 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 5d33fdc+dirty | 4.98 MiB | 6.55 MiB | 1.57 MiB |
| 5d33fdc+dirty | 4.98 MiB | 6.55 MiB | 1.57 MiB |
iOS (new) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| b0d3373+dirty | 3842.49 ms | 1218.49 ms | -2624.00 ms |
| ef27341+dirty | 3835.20 ms | 1212.23 ms | -2622.97 ms |
| e5bb5f6+dirty | 3825.74 ms | 1217.30 ms | -2608.43 ms |
| 44c8b3f+dirty | 3849.24 ms | 1209.94 ms | -2639.31 ms |
| 68672fc+dirty | 3832.22 ms | 1228.29 ms | -2603.93 ms |
| 5748023+dirty | 3844.74 ms | 1225.49 ms | -2619.26 ms |
| 6acdf1d+dirty | 3835.35 ms | 1218.30 ms | -2617.06 ms |
| 2c735cc+dirty | 1223.33 ms | 1224.38 ms | 1.04 ms |
| 100ce80+dirty | 3843.57 ms | 1226.46 ms | -2617.12 ms |
| 9210ae6+dirty | 3834.11 ms | 1216.64 ms | -2617.47 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| b0d3373+dirty | 5.15 MiB | 6.68 MiB | 1.53 MiB |
| ef27341+dirty | 5.15 MiB | 6.68 MiB | 1.53 MiB |
| e5bb5f6+dirty | 4.98 MiB | 6.51 MiB | 1.53 MiB |
| 44c8b3f+dirty | 5.15 MiB | 6.66 MiB | 1.51 MiB |
| 68672fc+dirty | 5.15 MiB | 6.71 MiB | 1.55 MiB |
| 5748023+dirty | 5.15 MiB | 6.68 MiB | 1.53 MiB |
| 6acdf1d+dirty | 4.98 MiB | 6.51 MiB | 1.53 MiB |
| 2c735cc+dirty | 3.38 MiB | 4.74 MiB | 1.35 MiB |
| 100ce80+dirty | 5.15 MiB | 6.67 MiB | 1.51 MiB |
| 9210ae6+dirty | 5.15 MiB | 6.68 MiB | 1.53 MiB |
Previous results on branch: fix/ios-feedback-screenshot-capture
Startup times
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 5d33fdc+dirty | 3855.06 ms | 1240.10 ms | -2614.96 ms |
| 5d33fdc+dirty | 3831.19 ms | 1208.00 ms | -2623.19 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 5d33fdc+dirty | 4.98 MiB | 6.55 MiB | 1.57 MiB |
| 5d33fdc+dirty | 4.98 MiB | 6.55 MiB | 1.57 MiB |
Android (legacy) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| f3215d3+dirty | 411.11 ms | 454.38 ms | 43.27 ms |
| d2eadf8+dirty | 414.64 ms | 454.56 ms | 39.92 ms |
| b0d3373+dirty | 557.66 ms | 579.42 ms | 21.76 ms |
| 27d9693+dirty | 419.08 ms | 469.12 ms | 50.04 ms |
| 4e0b819+dirty | 420.56 ms | 470.08 ms | 49.52 ms |
| 9474ead+dirty | 411.45 ms | 446.80 ms | 35.35 ms |
| 0bd8916+dirty | 412.77 ms | 451.31 ms | 38.54 ms |
| f9c1ed4+dirty | 431.00 ms | 466.22 ms | 35.22 ms |
| 3d536d1+dirty | 524.34 ms | 547.32 ms | 22.98 ms |
| 04207c4+dirty | 459.19 ms | 518.54 ms | 59.35 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| f3215d3+dirty | 48.30 MiB | 53.49 MiB | 5.19 MiB |
| d2eadf8+dirty | 48.30 MiB | 53.48 MiB | 5.18 MiB |
| b0d3373+dirty | 48.30 MiB | 53.58 MiB | 5.28 MiB |
| 27d9693+dirty | 49.74 MiB | 55.09 MiB | 5.34 MiB |
| 4e0b819+dirty | 49.74 MiB | 54.81 MiB | 5.07 MiB |
| 9474ead+dirty | 48.30 MiB | 53.61 MiB | 5.30 MiB |
| 0bd8916+dirty | 48.30 MiB | 53.57 MiB | 5.26 MiB |
| f9c1ed4+dirty | 49.74 MiB | 54.86 MiB | 5.12 MiB |
| 3d536d1+dirty | 49.74 MiB | 55.26 MiB | 5.52 MiB |
| 04207c4+dirty | 43.75 MiB | 48.12 MiB | 4.37 MiB |
Android (new) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| a50b33d+dirty | 353.21 ms | 398.48 ms | 45.27 ms |
| d038a14+dirty | 405.08 ms | 444.36 ms | 39.28 ms |
| 68672fc+dirty | 407.55 ms | 442.96 ms | 35.41 ms |
| ad66da3+dirty | 411.49 ms | 449.38 ms | 37.89 ms |
| f170ec3+dirty | 505.96 ms | 551.88 ms | 45.92 ms |
| 27d9693+dirty | 438.63 ms | 514.08 ms | 75.46 ms |
| 41d6254+dirty | 406.20 ms | 445.52 ms | 39.32 ms |
| 68ae91b+dirty | 515.04 ms | 578.08 ms | 63.04 ms |
| 5ee78d6+dirty | 411.18 ms | 437.83 ms | 26.65 ms |
| ae37560+dirty | 428.96 ms | 456.86 ms | 27.90 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| a50b33d+dirty | 43.94 MiB | 48.94 MiB | 5.00 MiB |
| d038a14+dirty | 48.30 MiB | 53.49 MiB | 5.19 MiB |
| 68672fc+dirty | 48.30 MiB | 53.61 MiB | 5.31 MiB |
| ad66da3+dirty | 48.30 MiB | 53.49 MiB | 5.19 MiB |
| f170ec3+dirty | 48.30 MiB | 53.57 MiB | 5.26 MiB |
| 27d9693+dirty | 49.74 MiB | 55.09 MiB | 5.34 MiB |
| 41d6254+dirty | 48.30 MiB | 53.60 MiB | 5.30 MiB |
| 68ae91b+dirty | 49.74 MiB | 54.79 MiB | 5.05 MiB |
| 5ee78d6+dirty | 48.30 MiB | 53.58 MiB | 5.28 MiB |
| ae37560+dirty | 48.30 MiB | 53.60 MiB | 5.29 MiB |
…nternal (#6380)" This reverts #6380 (eae9020), restoring the pre-8.19.0 iOS native behavior. #6380 made RNSentry access SentrySDK.internal before SentrySDK.start (during init, from RNSentryStart and from JS integrations calling native methods like fetchNativeAppStart/fetchNativeSdkInfo). Constructing SentrySDK.internal eagerly reads SentryDependencyContainer.screenshotSource — a lazy var that returns and permanently caches nil while startOptions is unset — breaking all iOS screenshot capture (Feedback Widget screenshot, attachScreenshot, Sentry.captureScreenshot). A targeted RN-side revert is unreliable (any pre-start SentrySDK.internal access poisons the lazy var, and JS controls the timing), so revert the whole migration as the interim fix. The migration can be re-landed once the underlying sentry-cocoa issue is fixed (screenshotSource should not permanently cache nil). Fixes #6497. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
d8e4ead to
62591e4
Compare
antonis
left a comment
There was a problem hiding this comment.
Verified that the CI is 🟢 and tested manually that screenshots are attached to eventsd https://sentry-sdks.sentry.io/issues/5005282269/events/a7b327b1ba934e22bb1d79a7ec66fd53/
Marking as blocked till we agree on the best solution for this.
| // to resolve it, breaking `RNSentryTimeToDisplayTests.swift` with | ||
| // `Undefined symbol: _TIME_TO_DISPLAY_ENTRIES_MAX_SIZE` at link time. | ||
| extern const int TIME_TO_DISPLAY_ENTRIES_MAX_SIZE; | ||
| static const int TIME_TO_DISPLAY_ENTRIES_MAX_SIZE = 50; |
There was a problem hiding this comment.
Reverting to static const reintroduces Swift link-time undefined symbol
Swift tests still reference TIME_TO_DISPLAY_ENTRIES_MAX_SIZE, but static const gives the constant internal linkage so Swift cannot resolve the external symbol at link time. Keep extern linkage or update the tests.
Evidence
RNSentryTimeToDisplayTests.swiftusesTIME_TO_DISPLAY_ENTRIES_MAX_SIZEat lines 19 and 57.- The removed comment explicitly documented that
static constcausesUndefined symbol: _TIME_TO_DISPLAY_ENTRIES_MAX_SIZEat link time because it lacks external linkage. - This test file is not modified in the PR, so it still expects the externally-visible symbol.
Identified by Warden code-review · 76B-VYK
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 62591e4. Configure here.
| fetchViewHierarchy : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) | ||
| { | ||
| #if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST | ||
| NSData *rawViewHierarchy = [RNSentryInternal captureViewHierarchy]; | ||
| if (rawViewHierarchy == nil) { | ||
| // Propagate the capture failure to JS instead of a truthy `[]`, which | ||
| // would be treated as a successful (empty) attachment. | ||
| resolve(nil); | ||
| return; | ||
| } | ||
| NSData *rawViewHierarchy = [PrivateSentrySDKOnly captureViewHierarchy]; | ||
|
|
||
| NSMutableArray *viewHierarchy = [NSMutableArray arrayWithCapacity:rawViewHierarchy.length]; | ||
| const char *bytes = (char *)[rawViewHierarchy bytes]; |
There was a problem hiding this comment.
Bug: When view hierarchy capture fails, the native code returns an empty array. This is treated as a truthy value in JS, resulting in a malformed, zero-byte attachment instead of null.
Severity: MEDIUM
Suggested Fix
Restore the nil check in RNSentry.mm to resolve the promise with nil when captureViewHierarchy fails. Alternatively, modify the JavaScript wrapper to check if the received array has a length greater than zero (raw && raw.length > 0) before creating a Uint8Array, ensuring null is returned for empty arrays.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: packages/core/ios/RNSentry.mm#L682-L688
Potential issue: When `PrivateSentrySDKOnly.captureViewHierarchy` returns `nil` in the
native iOS code, the promise is resolved with an empty `NSMutableArray` (`[]`). The
JavaScript wrapper receives this empty array, which is a truthy value in JavaScript.
Consequently, the wrapper returns an empty `new Uint8Array([])` instead of `null`. The
`viewhierarchy` integration then proceeds to attach a zero-byte, malformed
`view-hierarchy.json` file to the event, because the empty `Uint8Array` is also truthy.
This causes view hierarchy capture failures to silently produce corrupted attachments
instead of being correctly reported as failures.
Also affects:
packages/core/src/js/integrations/viewhierarchy.ts:34packages/core/src/js/wrapper.ts:750
Did we get this right? 👍 / 👎 to inform future reviews.

📢 Type of change
📜 Description
Since #6380 migrated iOS screenshot/view-hierarchy capture from
PrivateSentrySDKOnlyto the newSentrySDK.internalhybrid API,NATIVE.captureScreenshot()(andfetchViewHierarchy()) started returning empty on iOS.This affects three features on iOS:
feedbackE2E flow red).attachScreenshot: true— screenshots silently dropped from error events.attachViewHierarchy: true— view hierarchy silently dropped from error events.💡 Motivation and Context
The iOS
feedbackE2E flow (captureFlow-ios.yml) has been failing since #6380 was merged💚 How did you test it?
CI checks, Manual
📝 Checklist
sendDefaultPIIis enabled.🔮 Next steps