Summary
With runtimeVersion: { policy: "fingerprint" }, every cloud iOS build fails in the
CONFIGURE_EXPO_UPDATES phase because the fingerprint the CLI computes locally and
the one EAS computes on the worker disagree. The diff is always the same set of
autolinked native packages, and it is caused by phase ordering on the EAS side
rather than by anything in the project.
Android cloud builds of the same commit succeed.
Error
Runtime version mismatch:
- Runtime version calculated on local machine: ab094d00dac58fbe0f5fadf56e98ea99386c7e29
- Runtime version calculated on EAS: 4a1fb5698b8f39c7edc4be73f72628aa09437974
Six dir sources differ, all with reasons: ["rncoreAutolinkingIos"]:
| package |
local |
EAS |
@react-native-async-storage/async-storage |
c8abbd95… |
32dfd869… |
react-native-reanimated |
ba468ec2… |
0eb51f17… |
react-native-safe-area-context |
0a6f8823… |
6cf0f827… |
react-native-screens |
d1c1b1b2… |
4beef612… |
react-native-svg |
446ce667… |
5ad644c3… |
react-native-worklets |
97e4d9d9… |
58590582… |
Cause
The EAS build log runs the phases in this order:
INSTALL_PODS
POST_INSTALL_HOOK
CALCULATE_EXPO_UPDATES_RUNTIME_VERSION <-- fingerprint computed here
CONFIGURE_XCODE_PROJECT
CONFIGURE_EXPO_UPDATES <-- compared against the CLI's value here
The fingerprint is computed after pod install. CocoaPods mutates the
package directories it installs from, and the same log reports script phases
being added by four of the six packages above:
[!] RNSVG has added 2 script phases. …
[!] RNScreens has added 2 script phases. …
[!] RNWorklets has added 2 script phases. …
[!] react-native-safe-area-context has added 2 script phases. …
The CLI computes its value on a pristine checkout, before any pod install has
happened. A tree that has had pod install run against it can never hash the
same as one that has not, so the two values cannot agree by construction.
What was ruled out
Recorded because each of these looks plausible and costs a build to test.
- Stale
node_modules. rm -rf node_modules && npm ci produced
byte-identical hashes for all six directories — the exact "before" values
in the diff above. The local tree was already lockfile-faithful.
- Version drift against the lockfile. All six installed versions match
package-lock.json exactly.
- Build artifacts polluting
node_modules. No xcuserdata, build/, or
.DS_Store under any of the six.
- A stale local
ios/ directory being uploaded. ios/ is gitignored and
untracked, and the archive size was unchanged (134 MB → 133 MB) after adding
/ios to .easignore — it was never in the archive.
- A misconfigured
updates.url / project id. All three of owner,
extra.eas.projectId, and updates.url reference the same project.
Reproduction
- Expo SDK 55 project,
runtimeVersion: { policy: "fingerprint" }, with the six
packages above installed.
eas build --platform ios --profile development (cloud).
- Build fails in
CONFIGURE_EXPO_UPDATES with the mismatch above.
- The same commit builds successfully with
--local, where both values are
computed in the same environment.
Environment
|
|
| expo |
55.0.27 |
| expo-updates |
55.0.25 |
| @expo/fingerprint |
0.16.7 |
| eas-cli |
21.2.0 (pinned in devDependencies) |
| react-native |
0.83.6 |
| macOS |
26.4.1 |
| node |
22.16.0 |
Suggested fix
Compute the runtime version before INSTALL_PODS, so it is derived from the
same pristine tree the CLI sees. Alternatively, exclude CocoaPods-generated
content from rncoreAutolinkingIos directory hashing.
Local workaround
iOS builds run locally, where the CLI's fingerprint and the build's are computed
in the same environment:
cd mobile && npm run build:ios:device
Consequence for OTA: scripts/ota-preflight.mjs can only compare iOS
fingerprints meaningfully while iOS builds stay local. A cloud iOS build would
record a runtime version this repo can never reproduce, leaving iOS permanently
on fingerprint_changed.
Summary
With
runtimeVersion: { policy: "fingerprint" }, every cloud iOS build fails in theCONFIGURE_EXPO_UPDATESphase because the fingerprint the CLI computes locally andthe one EAS computes on the worker disagree. The diff is always the same set of
autolinked native packages, and it is caused by phase ordering on the EAS side
rather than by anything in the project.
Android cloud builds of the same commit succeed.
Error
Six
dirsources differ, all withreasons: ["rncoreAutolinkingIos"]:@react-native-async-storage/async-storagec8abbd95…32dfd869…react-native-reanimatedba468ec2…0eb51f17…react-native-safe-area-context0a6f8823…6cf0f827…react-native-screensd1c1b1b2…4beef612…react-native-svg446ce667…5ad644c3…react-native-worklets97e4d9d9…58590582…Cause
The EAS build log runs the phases in this order:
The fingerprint is computed after
pod install. CocoaPods mutates thepackage directories it installs from, and the same log reports script phases
being added by four of the six packages above:
The CLI computes its value on a pristine checkout, before any pod install has
happened. A tree that has had
pod installrun against it can never hash thesame as one that has not, so the two values cannot agree by construction.
What was ruled out
Recorded because each of these looks plausible and costs a build to test.
node_modules.rm -rf node_modules && npm ciproducedbyte-identical hashes for all six directories — the exact "before" values
in the diff above. The local tree was already lockfile-faithful.
package-lock.jsonexactly.node_modules. Noxcuserdata,build/, or.DS_Storeunder any of the six.ios/directory being uploaded.ios/is gitignored anduntracked, and the archive size was unchanged (134 MB → 133 MB) after adding
/iosto.easignore— it was never in the archive.updates.url/ project id. All three ofowner,extra.eas.projectId, andupdates.urlreference the same project.Reproduction
runtimeVersion: { policy: "fingerprint" }, with the sixpackages above installed.
eas build --platform ios --profile development(cloud).CONFIGURE_EXPO_UPDATESwith the mismatch above.--local, where both values arecomputed in the same environment.
Environment
Suggested fix
Compute the runtime version before
INSTALL_PODS, so it is derived from thesame pristine tree the CLI sees. Alternatively, exclude CocoaPods-generated
content from
rncoreAutolinkingIosdirectory hashing.Local workaround
iOS builds run locally, where the CLI's fingerprint and the build's are computed
in the same environment:
Consequence for OTA:
scripts/ota-preflight.mjscan only compare iOSfingerprints meaningfully while iOS builds stay local. A cloud iOS build would
record a runtime version this repo can never reproduce, leaving iOS permanently
on
fingerprint_changed.