Skip to content

Resolving hermes.framework Bitcode Error in Xcode 16

Problem Statement

When uploading iOS builds to TestFlight using Xcode 16 and macOS 15, you may encounter this validation failure:

Asset validation failed
NSLocalizedRecoverySuggestion=Invalid Executable. 
The executable 'TrueCaller.app/Frameworks/hermes.framework/hermes' contains bitcode.
(ID: 70394fde-4ed8-40ca-b6a8-2aabb46b397a)

Key characteristics:

  • Builds and archives successfully locally
  • Only fails during App Store Connect upload
  • Primarily affects React Native projects using Hermes engine
  • Occurs due to incompatible bitcode in framework binaries
  • Common after upgrading to Xcode 16/iOS 18 toolchains

Add a post-install script to your Podfile:

ruby
post_install do |installer|
  # Locate bitcode_strip tool
  bitcode_strip_path = `xcrun --find bitcode_strip`.chomp

  # Define stripping function
  def strip_bitcode(bitcode_strip_path, framework_path)
    full_path = File.join(Dir.pwd, framework_path)
    if File.exist?(full_path)
      system("#{bitcode_strip_path} #{full_path} -r -o #{full_path}")
      puts "✅ Stripped bitcode from #{framework_path}"
    end
  end

  # Hermes framework paths (add others as needed)
  frameworks_to_strip = [
    "Pods/hermes-engine/destroot/Library/Frameworks/macosx/hermes.framework/hermes",
    "Pods/hermes-engine/destroot/Library/Frameworks/macosx/hermes.framework/Versions/Current/hermes",
    "Pods/hermes-engine/destroot/Library/Frameworks/universal/hermes.xcframework/ios-arm64/hermes.framework/hermes",
    "Pods/hermes-engine/destroot/Library/Frameworks/universal/hermes.xcframework/ios-arm64_x86_64-maccatalyst/hermes.framework/hermes"
  ]

  # Execute stripping
  frameworks_to_strip.each do |framework|
    strip_bitcode(bitcode_strip_path, framework)
  end
end
  1. Add script above to your Podfile
  2. Run in terminal:
bash
cd ios && pod install
  1. Build a new archive
  2. Upload to App Store Connect

IMPORTANT

  • Verify paths match your Hermes installation location
  • Paths differ between React Native versions
  • Always backup your project before modifying Podfiles

Solution 2: Dynamic Framework Scanning (For Multiple Frameworks)

For projects with multiple problematic frameworks:

ruby
post_install do |installer|
  # [...] Your existing pod configurations

  # Recursive bitcode stripping function
  def strip_bitcode_from_frameworks
    frameworks = Dir.glob("Pods/**/*.framework")
    
    frameworks.each do |framework|
      binary_name = File.basename(framework, ".framework")
      binary_path = "#{framework}/#{binary_name}"
      
      next unless File.exist?(binary_path)
      
      # Check for bitcode presence
      if `otool -l "#{binary_path}"` =~ /__LLVM/
        puts "Stripping bitcode from: #{binary_path}"
        
        temp_path = "#{binary_path}_stripped"
        system("xcrun bitcode_strip \"#{binary_path}\" -r -o \"#{temp_path}\"")
        
        if File.exist?(temp_path)
          FileUtils.mv(temp_path, binary_path, force: true)
        end
      end
    end
  end

  strip_bitcode_from_frameworks()
end

Advantages:

  • Automatically detects all frameworks in Pods/
  • Checks for bitcode presence before stripping
  • No manual path configuration required

Explanation

Why This Happens

  • Xcode 16 changes: New bitcode validation rules for App Store submissions
  • Hermes incompatibility: React Native's Hermes engine ships with embedded bitcode
  • Framework limitations: Third-party binaries must not contain bitcode when uploading

Key Fix Components

  1. bitcode_strip tool

    • Provided by Xcode command-line tools
    • Removes bitcode sections from compiled binaries
    • -r flag removes entire bitcode segment
  2. Podfile post_install hook

    • Executes after pod install
    • Modifies frameworks before final build

Best Practices

  1. Always clear derived data after modifying Podfiles:
bash
rm -rf ~/Library/Developer/Xcode/DerivedData
  1. Verify bitcode removal with:
bash
otool -l your_framework.framework/your_framework | grep __LLVM
# Should return no output if bitcode is removed
  1. Update React Native regularly - newer versions may resolve this issue natively

Alternative Approach: Build Script

For non-Cocoapods projects, create a bash script:

bash
#!/bin/bash

find "${BUILD_PRODUCTS_DIR}" -name '*.framework' | while read framework; do
    binary="${framework}/$(basename "${framework}" .framework)"
    [[ "$(otool -l "$binary" | grep __LLVM)" ]] || continue
    
    echo "Stripping bitcode: ${binary}"
    xcrun bitcode_strip -r "$binary" -o "${binary}_stripped"
    mv "${binary}_stripped" "$binary"
done
  1. Add as "Run Script Phase" in Xcode build phases
  2. Place after framework embedding but before code signing

Additional Troubleshooting

If you see BoringSSL-GRPC warnings, add this to your post_install block:

ruby
installer.pods_project.targets.each do |target|
  if target.name == 'BoringSSL-GRPC'
    target.source_build_phase.files.each do |file|
      if file.settings && flags = file.settings['COMPILER_FLAGS']
        flags.sub!('-GCC_WARN_INHIBIT_ALL_WARNINGS', '')
      end
    end
  end
end

For React Native < 0.72, ensure proper Hermes configuration:

ruby
use_react_native!(
  :hermes_enabled => true,
  :fabric_enabled => false
)

Choose the solution that best fits your project structure. The targeted approach (Solution 1) is generally most reliable for Hermes-specific issues, while the dynamic scanner (Solution 2) offers broader protection against similar framework issues. After implementing, rebuild your project and test with TestFlight validation before App Store submission.

::: success VERIFICATION Confirm successful upload by:

  1. Running through Xcode's archive validation
  2. Checking for emails from App Store Connect within 30 minutes
  3. Seeing builds appear in TestFlight without "Processing" loops :::