Skip to content

[iOS] Prebuilt React-Core swap skipped on fresh first Debug build → Undefined symbols (DebugStringConvertible / Sealable) #57293

@N-M

Description

@N-M

Summary

replace-rncore-version.js (the [RNCore] Replace React Native Core for the right configuration script phase) silently keeps the Release prebuilt React.xcframework for a Debug build when no .last_build_configuration marker exists yet. The Release core is built with NDEBUG (RN_DEBUG_STRING_CONVERTIBLE=0), so a Debug app + from-source Fabric libraries fail to link.

This is a logic bug in the swap heuristic, not the (expected) fact that the prebuilt is shipped Release-by-default — the script is supposed to swap in the Debug variant and doesn't.

Environment

  • react-native 0.85.3 (heuristic is identical on main and 0.85-stable)
  • Xcode 16 / iOS Simulator (arm64), New Architecture, Hermes
  • Prebuilt React core in use (default: RCT_USE_PREBUILT_RNCORE=1)
  • Third-party Fabric libs built from source (expo-modules-core, react-native-gesture-handler, react-native-svg, react-native-screens, react-native-vision-camera, …)

Repro

  1. Fresh pod install (extracts React-Core-prebuilt — the Release xcframework — and writes no .last_build_configuration).
  2. Build the app in Debug for the simulator.

Actual

Undefined symbols for architecture arm64:
  "facebook::react::DebugStringConvertible::getDebugName() const", referenced from:
      vtable for expo::ExpoViewShadowNode<...> in libExpoModulesCore.a
      vtable for ...RNGestureHandlerButton... in libRNGestureHandler.a
  "facebook::react::Sealable::ensureUnsealed() const", referenced from: ...
  "typeinfo for facebook::react::BaseViewProps", ...
ld: symbol(s) not found for architecture arm64

Build log shows the swap script ran but decided to do nothing:

No previous build detected, but Debug Configuration. No need to replace React-Core-prebuilt

nm -gU React.framework/React | grep DebugStringConvertible0 symbols (Release core in place).

Root cause

scripts/replace-rncore-version.js:

// Assumption: if there is no stored last build, we assume that it was build for debug.
if (!fileExists && configuration === 'Debug') {
  return false; // skip swap
}

The assumption is inverted from reality: rncore.rb extracts the Release variant by default, so "no marker" means Release on disk, not Debug. The first Debug build therefore keeps the Release core. (RN_DEBUG_STRING_CONVERTIBLE is derived from NDEBUG in react/debug/flags.h, so the Release core has these symbols compiled out.)

Because the bug depends on hidden marker / build-order state, it is non-deterministic: doing a Release build first (which writes marker=Release) makes the next Debug build swap correctly, masking the problem.

Why this matters / relation to #57166

This is the upstream trigger for the common REACT_NATIVE_PRODUCTION=1 post_install workaround people add to silence the link error — which in turn causes the ViewProps ABI runtime crash reported in #57166 (self-closed as "not a core bug" without identifying this heuristic). Fixing the swap removes the need for that flag entirely: the Debug core gets installed, so both the link error and the ABI crash go away.
Also see mrousavy/react-native-vision-camera#4018.

Proposed fix

Drop the unsafe assumption so the swap always runs when the marker is absent (one extra extraction on the first build only):

// No marker => unknown variant on disk (CocoaPods extracts Release by default),
// so replace unconditionally to guarantee the core matches `configuration`.
return true;

(Alternative, more conservative: have rncore.rb write .last_build_configuration reflecting the variant it actually extracts, so the heuristic is always accurate.)

I'm happy to open a PR with the one-line fix if this direction is acceptable.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs: AttentionIssues where the author has responded to feedback.Needs: ReproThis issue could be improved with a clear list of steps to reproduce the issue.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions