Skip to content

Cloud iOS builds fail CONFIGURE_EXPO_UPDATES: runtime version computed after pod install, so it can never match the CLI's #4137

Description

@SocratesOne-Searjasub

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.

  1. 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.
  2. Version drift against the lockfile. All six installed versions match
    package-lock.json exactly.
  3. Build artifacts polluting node_modules. No xcuserdata, build/, or
    .DS_Store under any of the six.
  4. 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.
  5. A misconfigured updates.url / project id. All three of owner,
    extra.eas.projectId, and updates.url reference the same project.

Reproduction

  1. Expo SDK 55 project, runtimeVersion: { policy: "fingerprint" }, with the six
    packages above installed.
  2. eas build --platform ios --profile development (cloud).
  3. Build fails in CONFIGURE_EXPO_UPDATES with the mismatch above.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions