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
Recommended Solutions
Solution 1: Targeted Bitcode Removal (Recommended for Hermes Fix)
Add a post-install script to your Podfile
:
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
- Add script above to your
Podfile
- Run in terminal:
cd ios && pod install
- Build a new archive
- 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:
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
bitcode_strip
tool- Provided by Xcode command-line tools
- Removes bitcode sections from compiled binaries
-r
flag removes entire bitcode segment
Podfile
post_install
hook- Executes after
pod install
- Modifies frameworks before final build
- Executes after
Best Practices
- Always clear derived data after modifying Podfiles:
rm -rf ~/Library/Developer/Xcode/DerivedData
- Verify bitcode removal with:
otool -l your_framework.framework/your_framework | grep __LLVM
# Should return no output if bitcode is removed
- Update React Native regularly - newer versions may resolve this issue natively
Alternative Approach: Build Script
For non-Cocoapods projects, create a bash script:
#!/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
- Add as "Run Script Phase" in Xcode build phases
- Place after framework embedding but before code signing
Additional Troubleshooting
If you see BoringSSL-GRPC warnings, add this to your post_install
block:
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:
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:
- Running through Xcode's archive validation
- Checking for emails from App Store Connect within 30 minutes
- Seeing builds appear in TestFlight without "Processing" loops :::