From 7c7cd0c7ece548d8af7ba48ef49d61d175128360 Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Tue, 28 Jul 2026 16:02:50 +0200 Subject: [PATCH 1/2] fix(ios): make the RNSentry SPEC CHECKSUM in Podfile.lock machine-independent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CocoaPods stores external-source pods as an evaluated podspec JSON in Pods/Local Podspecs/ and derives the pod's SPEC CHECKSUM from that file, so the absolute ~/Library/Caches/... path we wrote into FRAMEWORK_SEARCH_PATHS leaked $HOME into Podfile.lock and made the RNSentry checksum differ per machine. Stage the cached Sentry.xcframework behind a symlink at Pods/sentry-xcframeworks//Sentry.xcframework and reference it as $(PODS_ROOT)/... instead. $(PODS_ROOT) is defined in both the per-pod and the user/aggregate xcconfigs and the link lives inside Pods/, so no Podfile-layout detection is needed and the string is identical on every machine — including machines overriding SENTRY_XCFRAMEWORK_CACHE_DIR. Fixes #6467 --- CHANGELOG.md | 6 +++ packages/core/RNSentry.podspec | 27 +++++++---- packages/core/scripts/sentry_utils.rb | 68 +++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ea29b7f08..26ddec1bcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,12 @@ When a root span ends (idle nav spans from `reactNavigationIntegration` / `expoRouterIntegration`, or a user's own `Sentry.startSpan(...)`), the integration writes `turbo_module...{call_count,duration_ms,error_count}` attributes plus summary keys (`turbo_module.total_call_count`, `turbo_module.total_duration_ms`, `turbo_module.top_module`). Async calls above `slowCallThresholdMs` (default 500ms) additionally record a `native.turbo_module` breadcrumb. Both surfaces enabled by default; new knobs `enableSpanAttribution`, `slowCallThresholdMs`, `maxTopModulesPerSpan` on `turboModuleContextIntegration`. +### Fixes + +- Make the `RNSentry` SPEC CHECKSUM in `Podfile.lock` machine-independent ([#6474](https://github.com/getsentry/sentry-react-native/pull/6474)) + + The prebuilt `Sentry.xcframework` is now referenced through a `$(PODS_ROOT)/sentry-xcframeworks/…` symlink instead of the absolute per-user cache path, so `Podfile.lock` no longer churns between developers and CI. Expect a one-time `RNSentry` checksum change on the next `pod install`; the `SENTRY_XCFRAMEWORK_CACHE_DIR=/tmp/…` workaround is no longer needed. + ### Changes - Expose `instrumentStateGraph` for manual LangGraph instrumentation ([#6520](https://github.com/getsentry/sentry-react-native/pull/6520)) diff --git a/packages/core/RNSentry.podspec b/packages/core/RNSentry.podspec index 4162b140dd..f2a8ac0601 100644 --- a/packages/core/RNSentry.podspec +++ b/packages/core/RNSentry.podspec @@ -130,16 +130,25 @@ Pod::Spec.new do |s| # sentry-cocoa's `Sentry.xcframework` layout is stable across releases. # Add a slice there if a future release ships one. # - # Point the search paths at the pod-install-time absolute path to the - # xcframework. `${PODS_TARGET_SRCROOT}` is only defined in per-pod - # xcconfigs, not in aggregate/user-target xcconfigs, and a - # `${PODS_ROOT}`-relative fallback works for one Podfile layout but - # breaks for another (e.g. the RN sample apps put node_modules at a - # different depth from RNSentryCocoaTester). Using the absolute path - # avoids the layout-detection dance — the path is regenerated on - # every `pod install`, so it's not something anyone commits. + # Reference the xcframework through a `Pods/sentry-xcframeworks/…` + # symlink so the search paths are machine-independent (see + # `stage_sentry_xcframework_in_pods`) — the values below feed the + # `RNSentry` SPEC CHECKSUM in `Podfile.lock`, so an absolute + # `~/Library/Caches/…` path here made the lockfile churn between + # machines (#6467). `${PODS_TARGET_SRCROOT}` can't be used instead: it + # is only defined in per-pod xcconfigs, not in aggregate/user-target + # xcconfigs, and a `${PODS_ROOT}`-relative path to node_modules works + # for one Podfile layout but breaks for another (e.g. the RN sample + # apps put node_modules at a different depth from RNSentryCocoaTester). + # When the link can't be staged — or the podspec is evaluated outside a + # `pod install`, where `$(PODS_ROOT)` would point at an unrelated + # sandbox — fall back to the absolute pod-install-time path: functional, + # but with a machine-specific checksum. + sentry_xcframework_ref = + stage_sentry_xcframework_in_pods(sentry_xcframework_dir, sentry_cocoa_version, 'Sentry') || + sentry_xcframework_dir xcframework_search_paths = SENTRY_XCFRAMEWORK_SLICES_BY_SDK.each_with_object({}) do |(sdk, slice_ids), acc| - paths = slice_ids.map { |slice| %("#{File.join(sentry_xcframework_dir, slice)}") } + paths = slice_ids.map { |slice| %("#{File.join(sentry_xcframework_ref, slice)}") } acc["FRAMEWORK_SEARCH_PATHS[sdk=#{sdk}*]"] = (['$(inherited)'] + paths).join(' ') end diff --git a/packages/core/scripts/sentry_utils.rb b/packages/core/scripts/sentry_utils.rb index 43a778c019..82cb751995 100644 --- a/packages/core/scripts/sentry_utils.rb +++ b/packages/core/scripts/sentry_utils.rb @@ -165,3 +165,71 @@ def ensure_sentry_xcframework(version, product = 'Sentry') target_dir end +# Directory inside the CocoaPods sandbox (`Pods/`) that holds the symlinks to +# the cached xcframeworks staged by `stage_sentry_xcframework_in_pods`. +SENTRY_XCFRAMEWORK_STAGING_DIR = 'sentry-xcframeworks'.freeze + +# Stage the cached xcframework behind a machine-independent `$(PODS_ROOT)` +# reference. +# +# Everything assigned to `pod_target_xcconfig`/`user_target_xcconfig` is part +# of the evaluated spec, and CocoaPods derives the pod's `SPEC CHECKSUM` in +# `Podfile.lock` from exactly that evaluated spec. Pointing +# `FRAMEWORK_SEARCH_PATHS` at the per-user cache directory +# (`~/Library/Caches/…`) therefore leaked `$HOME` into the checksum: two +# machines installing the same SDK version produced different `RNSentry` +# checksums and `Podfile.lock` churned on every `pod install` (#6467). +# +# Instead, symlink `Pods/sentry-xcframeworks//.xcframework` +# to the cached bundle and reference it as +# `$(PODS_ROOT)/sentry-xcframeworks/…` — a string that is identical on every +# machine (including ones overriding `SENTRY_XCFRAMEWORK_CACHE_DIR`). +# `$(PODS_ROOT)` is defined by CocoaPods in both the per-pod and the +# user/aggregate xcconfigs, unlike `$(PODS_TARGET_SRCROOT)`, and the link +# lives inside `Pods/` so no Podfile-layout detection is needed. +# +# Returns the `$(PODS_ROOT)`-based path, or nil when the link can't be staged +# — callers must then fall back to the absolute cache path (previous +# behaviour, functional but with a machine-specific checksum). +def stage_sentry_xcframework_in_pods(xcframework_dir, version, product = 'Sentry') + # Only stage when we're inside a real `pod install`, i.e. CocoaPods has + # located a Podfile and `sandbox_root` therefore points at the `Pods/` + # directory of the project that will consume the xcconfig. Anywhere else + # (`pod ipc spec`, `pod lib lint`, plain `Specification.from_file`) the + # sandbox root is unrelated to that project — CocoaPods evaluates a podspec + # with the CWD set to the podspec's own directory, so a guessed + # `/Pods` would stage the link inside `node_modules/@sentry/ + # react-native/` and `$(PODS_ROOT)/…` would dangle at build time. Bail out + # and let the caller use the absolute path, which is always correct. + return nil unless defined?(Pod::Config) && + Pod::Config.instance.respond_to?(:podfile_path) && + Pod::Config.instance.podfile_path + + staging_root = File.join(Pod::Config.instance.sandbox_root.to_s, SENTRY_XCFRAMEWORK_STAGING_DIR) + staging_dir = File.join(staging_root, version) + link_path = File.join(staging_dir, "#{product}.xcframework") + + FileUtils.mkdir_p(staging_dir) + # Recreate the link when it's missing or points at a stale location (e.g. + # `SENTRY_XCFRAMEWORK_CACHE_DIR` changed between installs). `rm_rf` unlinks + # the symlink itself, it does not follow it into the shared cache. + unless File.symlink?(link_path) && File.readlink(link_path) == xcframework_dir + FileUtils.rm_rf(link_path) + File.symlink(xcframework_dir, link_path) + end + + # Drop links staged for other sentry-cocoa versions (left behind by SDK + # upgrades) so the directory doesn't collect dangling symlinks. + Dir.glob(File.join(staging_root, '*')).each do |stale| + FileUtils.rm_rf(stale) unless File.basename(stale) == version + end + + "$(PODS_ROOT)/#{SENTRY_XCFRAMEWORK_STAGING_DIR}/#{version}/#{product}.xcframework" +rescue StandardError, NotImplementedError => e + if defined?(Pod::UI) + Pod::UI.warn "[Sentry] Could not link the #{product} xcframework into Pods/ " \ + "(#{e.class}: #{e.message}). Falling back to the absolute cache path; " \ + "the RNSentry checksum in Podfile.lock will be machine-specific." + end + nil +end From dc4206f02a13e0a07f32d900f08aa820747548cd Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Thu, 30 Jul 2026 14:00:50 +0200 Subject: [PATCH 2/2] docs(changelog): move entry to Unreleased and reference #6534 The 8.21.0 release was cut while this PR was open, so the merge from main landed the entry inside the released section and reordered an unrelated Session Replay item. Restore 8.21.0 verbatim from main and add a fresh Unreleased section. --- CHANGELOG.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18c8300a98..98d5a772e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ > make sure you follow our [migration guide](https://docs.sentry.io/platforms/react-native/migration/) first. +## Unreleased + +### Fixes + +- Make the `RNSentry` SPEC CHECKSUM in `Podfile.lock` machine-independent ([#6534](https://github.com/getsentry/sentry-react-native/pull/6534)) + + The prebuilt `Sentry.xcframework` is now referenced through a `$(PODS_ROOT)/sentry-xcframeworks/…` symlink instead of the absolute per-user cache path, so `Podfile.lock` no longer churns between developers and CI. Expect a one-time `RNSentry` checksum change on the next `pod install`; the `SENTRY_XCFRAMEWORK_CACHE_DIR=/tmp/…` workaround is no longer needed. + ## 8.21.0 ### Features @@ -15,13 +23,10 @@ When a root span ends (idle nav spans from `reactNavigationIntegration` / `expoRouterIntegration`, or a user's own `Sentry.startSpan(...)`), the integration writes `turbo_module...{call_count,duration_ms,error_count}` attributes plus summary keys (`turbo_module.total_call_count`, `turbo_module.total_duration_ms`, `turbo_module.top_module`). Async calls above `slowCallThresholdMs` (default 500ms) additionally record a `native.turbo_module` breadcrumb. Both surfaces enabled by default; new knobs `enableSpanAttribution`, `slowCallThresholdMs`, `maxTopModulesPerSpan` on `turboModuleContextIntegration`. -### Fixes - -- Make the `RNSentry` SPEC CHECKSUM in `Podfile.lock` machine-independent ([#6474](https://github.com/getsentry/sentry-react-native/pull/6474)) - - The prebuilt `Sentry.xcframework` is now referenced through a `$(PODS_ROOT)/sentry-xcframeworks/…` symlink instead of the absolute per-user cache path, so `Podfile.lock` no longer churns between developers and CI. Expect a one-time `RNSentry` checksum change on the next `pod install`; the `SENTRY_XCFRAMEWORK_CACHE_DIR=/tmp/…` workaround is no longer needed. - Session Replay network details now capture `fetch` (Blob/ArrayBuffer) response bodies ([#6473](https://github.com/getsentry/sentry-react-native/pull/6473), [#6533](https://github.com/getsentry/sentry-react-native/pull/6533)) +### Fixes + - Fix duplicate navigation transaction on Expo Router `withAnchor` navigations ([#6439](https://github.com/getsentry/sentry-react-native/pull/6439)) ### Changes