Environment
- Expo SDK: 55
- eas-cli: 18.6.0
- Runtime version policy:
fingerprint
- Platform: Android (also affects iOS)
Description
When using the fingerprint runtime version policy, eas build (cloud) and eas update (local) compute different fingerprints for what should be an identical project state. This causes OTA updates to be rejected on devices because the update's runtime version does not match the build's runtime version.
Steps to Reproduce
- Configure
runtimeVersion.policy: "fingerprint" in eas.json.
- Run
eas build --platform android (cloud build).
- Note the runtime version from
eas build:view (the fingerprint hash).
- Without changing
package.json or package-lock.json, run eas update locally.
- The update is published with a different fingerprint than the build, making it incompatible.
To compare fingerprints directly:
# Local fingerprint
npx @expo/fingerprint fingerprint:generate --platform android --debug
# Cloud fingerprint
eas build:view <BUILD_ID> # check runtimeVersion field
Expected Behavior
The fingerprint computed locally (for eas update) should match the fingerprint computed during eas build, given the same package.json and package-lock.json. OTA updates should be deliverable to devices built with eas build without manual version pinning.
Actual Behavior
The fingerprints differ. Using --debug output from npx @expo/fingerprint fingerprint:generate, we identified that the fingerprint hash includes sources that can legitimately differ between the local environment and the cloud build environment:
.easignore — controls which files are excluded from the upload. The cloud server may have different file presence based on what was uploaded vs. what exists locally.
eas.json — the build configuration file is present locally but may be excluded or treated differently in the cloud environment.
packageJson:scripts — the scripts section of package.json is included in the fingerprint. If npm install on the cloud server produces any side effects that alter package.json, or if the cloud environment evaluates scripts differently, this diverges.
The core issue is that eas build computes the fingerprint on the EAS server after npm install runs in the cloud environment. This means the fingerprint is computed from a post-install state that the local machine cannot fully replicate, even with a committed package-lock.json.
Root Cause Analysis
The fingerprint should ideally be computed from the uploaded source snapshot (the archive sent to EAS), not from the post-install state on the cloud server. If both eas build and eas update computed fingerprints from the same source state (the committed/uploaded files), they would agree.
Alternatively, eas update could accept a --fingerprint flag to explicitly pass the fingerprint from a known build, but this requires users to manually look up and pass the hash.
Workaround
We pin the runtime version via an environment variable to avoid the mismatch:
- In
app.config.js:
runtimeVersion: process.env.RUNTIME_VERSION ?? { policy: "fingerprint" },
- After a build, retrieve the fingerprint:
eas build:view <BUILD_ID> # note the runtimeVersion hash
- Pass it explicitly when publishing an update:
RUNTIME_VERSION=<hash-from-build> eas update --channel production
This is fragile and error-prone — if you forget to pass the env var, the update ships with the wrong fingerprint and silently fails to be delivered.
Suggested Fix
One of the following would resolve this:
- Compute the fingerprint from the uploaded archive on the EAS server, not from the post-install state. This ensures the fingerprint is reproducible locally from the same source files.
- Expose
eas update --runtime-version <fingerprint> as a first-class flag and document the workflow of looking up a build's fingerprint and passing it to eas update.
- Exclude EAS-specific config files (
.easignore, eas.json) from fingerprint computation, since these are infrastructure files that vary between local and cloud contexts and should not affect the runtime compatibility of an app.
Environment
fingerprintDescription
When using the
fingerprintruntime version policy,eas build(cloud) andeas update(local) compute different fingerprints for what should be an identical project state. This causes OTA updates to be rejected on devices because the update's runtime version does not match the build's runtime version.Steps to Reproduce
runtimeVersion.policy: "fingerprint"ineas.json.eas build --platform android(cloud build).eas build:view(the fingerprint hash).package.jsonorpackage-lock.json, runeas updatelocally.To compare fingerprints directly:
Expected Behavior
The fingerprint computed locally (for
eas update) should match the fingerprint computed duringeas build, given the samepackage.jsonandpackage-lock.json. OTA updates should be deliverable to devices built witheas buildwithout manual version pinning.Actual Behavior
The fingerprints differ. Using
--debugoutput fromnpx @expo/fingerprint fingerprint:generate, we identified that the fingerprint hash includes sources that can legitimately differ between the local environment and the cloud build environment:.easignore— controls which files are excluded from the upload. The cloud server may have different file presence based on what was uploaded vs. what exists locally.eas.json— the build configuration file is present locally but may be excluded or treated differently in the cloud environment.packageJson:scripts— thescriptssection ofpackage.jsonis included in the fingerprint. Ifnpm installon the cloud server produces any side effects that alterpackage.json, or if the cloud environment evaluates scripts differently, this diverges.The core issue is that
eas buildcomputes the fingerprint on the EAS server afternpm installruns in the cloud environment. This means the fingerprint is computed from a post-install state that the local machine cannot fully replicate, even with a committedpackage-lock.json.Root Cause Analysis
The fingerprint should ideally be computed from the uploaded source snapshot (the archive sent to EAS), not from the post-install state on the cloud server. If both
eas buildandeas updatecomputed fingerprints from the same source state (the committed/uploaded files), they would agree.Alternatively,
eas updatecould accept a--fingerprintflag to explicitly pass the fingerprint from a known build, but this requires users to manually look up and pass the hash.Workaround
We pin the runtime version via an environment variable to avoid the mismatch:
app.config.js:This is fragile and error-prone — if you forget to pass the env var, the update ships with the wrong fingerprint and silently fails to be delivered.
Suggested Fix
One of the following would resolve this:
eas update --runtime-version <fingerprint>as a first-class flag and document the workflow of looking up a build's fingerprint and passing it toeas update..easignore,eas.json) from fingerprint computation, since these are infrastructure files that vary between local and cloud contexts and should not affect the runtime compatibility of an app.