Skip to content

fix(ios): Detect dynamic linkage from the Podfile when USE_FRAMEWORKS is unset - #6621

Closed
antonis wants to merge 7 commits into
mainfrom
fix/ios-linkage-detection-warden
Closed

fix(ios): Detect dynamic linkage from the Podfile when USE_FRAMEWORKS is unset#6621
antonis wants to merge 7 commits into
mainfrom
fix/ios-linkage-detection-warden

Conversation

@antonis

@antonis antonis commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

📢 Type of change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring

📜 Description

Stacked on top of #6615.

The -force_load flag for the Sentry static archive has to ride whichever target's link does the ObjC-category stripping: the app link under static linkage (user_target_xcconfig), or the RNSentry dylib link under dynamic frameworks (pod_target_xcconfig) — never both, or a second copy of Sentry lands in the app.

Previously that choice was made solely from ENV['USE_FRAMEWORKS'] == 'dynamic'. This PR introduces sentry_uses_dynamic_linkage (in scripts/sentry_utils.rb):

  • USE_FRAMEWORKS is honored as an explicit override when set (dynamic → dynamic path, anything else → static).
  • When it is unset, the linkage is read off the Podfile CocoaPods has already loaded (target_definition_list … build_type.dynamic_framework?).
  • Outside a real pod install (pod ipc spec, pod lib lint) there is no Podfile, so it falls back to the static default — correct for the RN default.

💡 Motivation and Context

CocoaPods does not export USE_FRAMEWORKS for a bare use_frameworks! / use_frameworks! :linkage => :dynamic in a consumer's Podfile. Such a project therefore took the static branch and force-loaded Sentry into the app binary while RNSentry was really a dylib — a second Sentry copy in the app, and the framework's own link still dead-stripped the replay network-details category (the #6609 crash class). Reading the Podfile closes that gap.

This is a follow-up to the warden feedback on #6615. It is a best-effort proxy: the linkage that ultimately governs is RNSentry's own pod build_type, computed only after pre_install hooks run and unavailable at podspec-eval time, so the declared target linkage is used instead (matching, and widening, what the env var proxied).

💚 How did you test it?

  • ruby -c on both touched files — Syntax OK.
  • Verified across six linkage scenarios (env=dynamic; env=static overriding a dynamic Podfile; no env + bare use_frameworks! dynamic → the gap, now closed; no env + static Podfile; no env + use_frameworks! :linkage => :static; no env + no Podfile for lint/ipc) — each resolves to the correct target.
  • Behavior is unchanged for this repo's CI matrix and both samples: the dynamic matrix sets USE_FRAMEWORKS=dynamic (env override), and the no-frameworks matrix leaves it unset while the sample Podfiles only call use_frameworks! when the env var is set → introspection returns false → same user_target path as before.

📝 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.
  • Public API changes reviewed by another Mobile SDK team member or implemented according to the develop docs spec.
  • No breaking changes.

🔮 Next steps

Merge #6615 first (or retarget this PR to main once #6615 lands). Known limitation: a project that globally enables use_frameworks! but forces RNSentry itself static without setting USE_FRAMEWORKS would read as dynamic; set USE_FRAMEWORKS=static to override.

🤖 Generated with Claude Code

antonis and others added 6 commits August 26, 2026 14:14
…methods

RN links sentry-cocoa as a prebuilt static xcframework with no whole-archive
linker flag. Objective-C category methods (Swift `@objc extension`s, e.g.
`SentryReplayNetworkDetails+Capture` in cocoa 9.25/9.26) are therefore
dead-stripped at the app's final link and crash at runtime with
`unrecognized selector`.

Add per-SDK `-force_load` of the Sentry binary to `user_target_xcconfig` (the
app target, where the stripping link happens). Scoped to the Sentry archive —
unlike whole-link `-ObjC`/`-all_load` — and, unlike `-ObjC`, also retains
Swift-only metadata, so it hardens against future valid-but-strippable
upstream changes rather than only the current category case.

Apple QA1490 documents category stripping as the consumer's responsibility.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Under `use_frameworks! :linkage => :dynamic`, `@import Sentry` pulls the
static Sentry archive into the RNSentry dylib, so RNSentry's own link is
what dead-strips the `SentryReplayNetworkDetails+Capture` ObjC category —
not the app link. Placing `-force_load` only on `user_target_xcconfig`
missed that link, and additionally risked forcing a second copy of Sentry
into the app binary (duplicate ObjC classes, two SentrySDK singletons).

Detect linkage via `ENV['USE_FRAMEWORKS']` (the signal RN's Podfile setup
and our samples already use) and put `-force_load` on exactly one target:
`pod_target_xcconfig` (RNSentry dylib link) for dynamic, otherwise
`user_target_xcconfig` (app link) for the static default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Switch the link-time fix for the stripped `SentryReplayNetworkDetails`
ObjC category from `-force_load <Sentry binary>` to `-ObjC`:

- `-ObjC` names no archive, so it only affects static libs actually in the
  current link — the same flag is correct on both the app target (static
  linkage) and the RNSentry pod target (dynamic linkage) with no risk of
  pulling a second copy of Sentry into the app. This removes the need to
  detect linkage via `ENV['USE_FRAMEWORKS']`, closing the gap where a bare
  `use_frameworks!` (env var unset) mis-placed the flag.
- It is Apple's documented fix for this exact problem (QA1490).
- It only retains ObjC class/category objects (not the whole archive) and
  needs no per-slice binary paths, simplifying the podspec.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The -ObjC swap (dc780c5) regressed the "Test ios production REV2" job:
captureSpaceflightNewsScreenTransaction fails with the #6609 crash ("No
visible element found: Load More Articles") because the app crashes on the
replay network-detail path. -force_load (8c1a078) passes the same test
(19/19). Verified directly from CI logs of both runs.

Root cause confirmed against the checksum-verified released 9.26.0 slice:
-setRequestWithSize:bodyData:contentType:allHeaders:configuredHeaders: lives
only in the category-only object SentryReplayNetworkDetails+Capture.o (no
class of its own), so a plain static link dead-strips it. -force_load loads
the whole archive unconditionally; -ObjC does not rescue it in the real
production build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… is unset

The `-force_load` flag for the Sentry static archive must ride whichever
target's link does the ObjC-category stripping: the app link under static
linkage (`user_target_xcconfig`), or the RNSentry dylib link under dynamic
frameworks (`pod_target_xcconfig`).

Previously this was decided solely by `ENV['USE_FRAMEWORKS'] == 'dynamic'`.
CocoaPods does not export that env var for a bare `use_frameworks!` /
`use_frameworks! :linkage => :dynamic` in a consumer's Podfile, so such a
setup wrongly took the static path — force-loading Sentry into the app
while RNSentry is really a dylib (a second Sentry copy in the app, and the
framework link still strips the replay category, #6609).

Add `sentry_uses_dynamic_linkage`: honor `USE_FRAMEWORKS` as an explicit
override when set, otherwise read the declared linkage off the Podfile
CocoaPods has already loaded. Behavior is unchanged for this repo's CI and
both samples (which set the env var, or don't use frameworks at all); it
only newly covers the bare-`use_frameworks!` consumer that the env-only
check missed. Falls back to the static default outside a real `pod install`
(`pod ipc spec`, `pod lib lint`), where no Podfile is available.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

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(ios): Detect dynamic linkage from the Podfile when USE_FRAMEWORKS is unset by antonis in #6621
  • chore(deps): update Cocoa SDK to v9.26.1 by github-actions in #6623
  • fix(ios): Force-load the Sentry static archive to keep ObjC category methods by antonis in #6615
  • feat(core): Forward feature flag evaluations to the native SDKs by antonis in #6613
  • chore(deps): update Android SDK to v8.54.0 by github-actions in #6624
  • chore(deps): update Sentry Android Gradle Plugin to v6.20.0 by github-actions in #6627

🤖 This preview updates automatically when you update the PR.

@sentry

sentry Bot commented Aug 27, 2026

Copy link
Copy Markdown

📲 Install Builds

Android

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

⚙️ sentry-react-native Build Distribution Settings

@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

iOS (legacy) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 3829.79 ms 1220.15 ms -2609.63 ms
Size 5.08 MiB 6.80 MiB 1.72 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
64630e5+dirty 3842.70 ms 1218.11 ms -2624.60 ms
b0d3373+dirty 3831.75 ms 1227.29 ms -2604.46 ms
b04af96+dirty 3818.92 ms 1219.76 ms -2599.16 ms
3d31fcf+dirty 3838.09 ms 1223.46 ms -2614.63 ms
a0a3177+dirty 3844.73 ms 1225.23 ms -2619.51 ms
af33f3b+dirty 3849.98 ms 1236.45 ms -2613.53 ms
09a902f+dirty 3835.67 ms 1217.11 ms -2618.57 ms
5a316ea+dirty 3820.11 ms 1211.28 ms -2608.83 ms
403b9f3+dirty 3832.18 ms 1213.32 ms -2618.87 ms
acd838e+dirty 3849.78 ms 1230.00 ms -2619.78 ms

App size

Revision Plain With Sentry Diff
64630e5+dirty 4.98 MiB 6.46 MiB 1.49 MiB
b0d3373+dirty 5.15 MiB 6.68 MiB 1.53 MiB
b04af96+dirty 4.98 MiB 6.54 MiB 1.56 MiB
3d31fcf+dirty 4.98 MiB 6.56 MiB 1.58 MiB
a0a3177+dirty 4.98 MiB 6.55 MiB 1.58 MiB
af33f3b+dirty 4.98 MiB 6.51 MiB 1.54 MiB
09a902f+dirty 4.98 MiB 6.46 MiB 1.49 MiB
5a316ea+dirty 4.98 MiB 6.51 MiB 1.53 MiB
403b9f3+dirty 5.08 MiB 6.72 MiB 1.64 MiB
acd838e+dirty 5.15 MiB 6.70 MiB 1.55 MiB

Previous results on branch: fix/ios-linkage-detection-warden

Startup times

Revision Plain With Sentry Diff
2ec1a06+dirty 3837.33 ms 1204.12 ms -2633.21 ms

App size

Revision Plain With Sentry Diff
2ec1a06+dirty 5.08 MiB 6.78 MiB 1.71 MiB

@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

iOS (new) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 3838.43 ms 1212.65 ms -2625.78 ms
Size 5.08 MiB 6.80 MiB 1.72 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
3ce5254+dirty 1217.70 ms 1224.69 ms 6.99 ms
0b1b5e3+dirty 3820.72 ms 1207.94 ms -2612.78 ms
44abcc2+dirty 3841.42 ms 1214.77 ms -2626.65 ms
1d3572b+dirty 3830.71 ms 1222.23 ms -2608.49 ms
6177334+dirty 3851.52 ms 1226.23 ms -2625.29 ms
5ee78d6+dirty 3815.71 ms 1217.04 ms -2598.67 ms
083e3ef+dirty 3871.96 ms 1234.95 ms -2637.01 ms
ce7b368+dirty 3851.41 ms 1222.37 ms -2629.04 ms
7887847+dirty 3844.89 ms 1221.67 ms -2623.22 ms
1122a96+dirty 3839.17 ms 1219.23 ms -2619.93 ms

App size

Revision Plain With Sentry Diff
3ce5254+dirty 3.38 MiB 4.76 MiB 1.38 MiB
0b1b5e3+dirty 5.15 MiB 6.70 MiB 1.54 MiB
44abcc2+dirty 4.98 MiB 6.55 MiB 1.57 MiB
1d3572b+dirty 4.98 MiB 6.56 MiB 1.58 MiB
6177334+dirty 5.15 MiB 6.68 MiB 1.53 MiB
5ee78d6+dirty 5.15 MiB 6.69 MiB 1.53 MiB
083e3ef+dirty 4.98 MiB 6.55 MiB 1.57 MiB
ce7b368+dirty 4.98 MiB 6.51 MiB 1.53 MiB
7887847+dirty 4.98 MiB 6.46 MiB 1.48 MiB
1122a96+dirty 5.15 MiB 6.68 MiB 1.53 MiB

Previous results on branch: fix/ios-linkage-detection-warden

Startup times

Revision Plain With Sentry Diff
2ec1a06+dirty 3851.27 ms 1222.76 ms -2628.51 ms

App size

Revision Plain With Sentry Diff
2ec1a06+dirty 5.08 MiB 6.78 MiB 1.71 MiB

@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Android (new) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 438.02 ms 453.29 ms 15.27 ms
Size 49.74 MiB 55.66 MiB 5.91 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
5a010b7+dirty 475.15 ms 519.02 ms 43.88 ms
f9c1ed4+dirty 520.57 ms 594.84 ms 74.27 ms
c2e182c+dirty 468.50 ms 545.44 ms 76.94 ms
2c735cc+dirty 435.20 ms 459.48 ms 24.28 ms
af33f3b+dirty 417.68 ms 448.04 ms 30.37 ms
7d8c8bd+dirty 406.06 ms 460.88 ms 54.81 ms
890d145+dirty 486.42 ms 514.85 ms 28.43 ms
ef27341+dirty 519.02 ms 553.42 ms 34.40 ms
bf168a4+dirty 430.60 ms 459.31 ms 28.71 ms
41d6254+dirty 406.20 ms 445.52 ms 39.32 ms

App size

Revision Plain With Sentry Diff
5a010b7+dirty 48.30 MiB 53.58 MiB 5.28 MiB
f9c1ed4+dirty 49.74 MiB 54.86 MiB 5.12 MiB
c2e182c+dirty 49.74 MiB 54.85 MiB 5.11 MiB
2c735cc+dirty 43.94 MiB 48.94 MiB 5.00 MiB
af33f3b+dirty 49.74 MiB 55.09 MiB 5.35 MiB
7d8c8bd+dirty 48.30 MiB 53.54 MiB 5.23 MiB
890d145+dirty 43.94 MiB 49.00 MiB 5.06 MiB
ef27341+dirty 48.30 MiB 53.54 MiB 5.24 MiB
bf168a4+dirty 49.74 MiB 55.09 MiB 5.35 MiB
41d6254+dirty 48.30 MiB 53.60 MiB 5.30 MiB

Previous results on branch: fix/ios-linkage-detection-warden

Startup times

Revision Plain With Sentry Diff
2ec1a06+dirty 519.78 ms 580.26 ms 60.48 ms

App size

Revision Plain With Sentry Diff
2ec1a06+dirty 49.74 MiB 55.45 MiB 5.71 MiB

@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Android (legacy) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 430.53 ms 440.09 ms 9.56 ms
Size 49.74 MiB 55.66 MiB 5.91 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
8e18f68+dirty 426.20 ms 456.44 ms 30.24 ms
038a6d7+dirty 524.82 ms 531.92 ms 7.10 ms
a858ac3+dirty 426.50 ms 484.82 ms 58.32 ms
822d35b+dirty 429.31 ms 498.04 ms 68.73 ms
5ca03f9+dirty 423.30 ms 467.00 ms 43.70 ms
083e3ef+dirty 446.67 ms 491.68 ms 45.01 ms
4953e94+dirty 442.02 ms 456.52 ms 14.50 ms
244f6e8+dirty 424.36 ms 488.29 ms 63.92 ms
2e64504+dirty 450.52 ms 482.70 ms 32.18 ms
a9df5ce+dirty 418.40 ms 452.38 ms 33.98 ms

App size

Revision Plain With Sentry Diff
8e18f68+dirty 49.74 MiB 55.45 MiB 5.71 MiB
038a6d7+dirty 48.30 MiB 53.60 MiB 5.30 MiB
a858ac3+dirty 49.74 MiB 55.08 MiB 5.34 MiB
822d35b+dirty 49.74 MiB 54.84 MiB 5.10 MiB
5ca03f9+dirty 49.74 MiB 55.26 MiB 5.52 MiB
083e3ef+dirty 49.74 MiB 55.36 MiB 5.61 MiB
4953e94+dirty 43.75 MiB 48.08 MiB 4.33 MiB
244f6e8+dirty 49.74 MiB 55.38 MiB 5.63 MiB
2e64504+dirty 49.74 MiB 55.45 MiB 5.71 MiB
a9df5ce+dirty 49.74 MiB 55.36 MiB 5.62 MiB

Previous results on branch: fix/ios-linkage-detection-warden

Startup times

Revision Plain With Sentry Diff
2ec1a06+dirty 537.83 ms 586.57 ms 48.74 ms

App size

Revision Plain With Sentry Diff
2ec1a06+dirty 49.74 MiB 55.45 MiB 5.71 MiB

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 2a2fb0d. Configure here.

@antonis
antonis marked this pull request as ready for review August 27, 2026 14:26
Comment thread packages/core/scripts/sentry_utils.rb
Base automatically changed from fix/ios-force-load-sentry-xcframework to main August 28, 2026 08:25
…tion-warden

# Conflicts:
#	packages/core/RNSentry.podspec
Comment on lines +267 to +269
podfile.target_definition_list.any? do |td|
!td.root? && (td.build_type.dynamic_framework? rescue false)
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The sentry_uses_dynamic_linkage check is too broad. It incorrectly detects dynamic linkage in mixed-target projects, causing build flags to be misapplied, which can lead to runtime crashes.
Severity: HIGH

Suggested Fix

The logic should be changed to check the linkage setting of the specific target that actually includes the RNSentry pod, rather than checking if any target in the Podfile uses dynamic linkage. This will ensure the -force_load flag is correctly applied based on RNSentry's context.

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/scripts/sentry_utils.rb#L267-L269

Potential issue: The `sentry_uses_dynamic_linkage` function incorrectly determines the
linkage type in projects with multiple targets and mixed linkage settings. It checks if
`any?` target uses dynamic frameworks, which can return `true` even if the target
containing `RNSentry` uses static linkage. This misdetection leads to the `-force_load`
build flag being applied to the wrong configuration (`pod_target_xcconfig` instead of
the user target's xcconfig). As a result, necessary Objective-C category methods are
stripped during the link phase for the static target, causing the application to crash
at runtime.

@antonis
antonis marked this pull request as draft August 28, 2026 09:45
@antonis antonis closed this Aug 28, 2026
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 skip-changelog

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant