Skip to content

fingerprint runtime version: eas build (cloud) and eas update (local) produce mismatched fingerprints #3602

Description

@MegaJ

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

  1. Configure runtimeVersion.policy: "fingerprint" in eas.json.
  2. Run eas build --platform android (cloud build).
  3. Note the runtime version from eas build:view (the fingerprint hash).
  4. Without changing package.json or package-lock.json, run eas update locally.
  5. 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:

  1. In app.config.js:
runtimeVersion: process.env.RUNTIME_VERSION ?? { policy: "fingerprint" },
  1. After a build, retrieve the fingerprint:
eas build:view <BUILD_ID>  # note the runtimeVersion hash
  1. 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:

  1. 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.
  2. 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.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions