Skip to content

Repack stamps build number 1 (CFBundleVersion / versionCode) when appVersionSource is remote #4458

Description

@jonstuebe

Build/Submit details page URL

https://expo.dev/accounts/oneraven/projects/home/builds/35f6ad0b-9b74-47db-8663-6f5baeb295e5 (an iOS repack)

Summary

With cli.appVersionSource: "remote", a type: repack workflow job stamps the binary with build number 1 on iOS (CFBundleVersion), and the same code stamps 1 on Android (versionCode). EAS still records the incremented remote build number for the repacked build. The binary should carry the build number EAS assigned to the repack, or at least the source build's number.

Here's what we saw on iOS internal-distribution builds from the same profile (autoIncrement: true):

Build Kind EAS appBuildVersion CFBundleVersion in the IPA
47 full build 47 47
62–66 repack 62–66 1

On Android this blocks installs. A repacked APK with versionCode 1 can't be installed over the source build (296 in our case): Android rejects it as a version downgrade. So we've had to turn repack off.

Cause

EAS's repack step (packages/build-tools in this repo) passes @expo/repack-app no version information (packages/build-tools/src/steps/functions/repack.ts: repackAppIosAsync and repackAppAndroidAsync get the project root, source app, signing options and env). @expo/repack-app 0.10.3 then reads the build number from the project's Expo config and falls back to 1:

  • iOS: buildNumber = config.ios?.buildNumber || "1", written to CFBundleVersion in the app's and every app extension's Info.plist.
  • Android: versionCode = Number(config.android?.versionCode ?? 1), written to android:versionCode in the manifest.

Under a remote version source the Expo config has no ios.buildNumber or android.versionCode. EAS keeps the number server side and injects it during a full build (CONFIGURE_ANDROID_VERSION / the iOS equivalent), so the fallback always wins on a repack.

Evidence: iOS we verified by unpacking the IPAs. For Android we read the @expo/repack-app 0.10.3 source; we didn't ship an Android repack, because the downgrade would have blocked installs.

Managed or bare?

Managed (CNG, expo prebuild on the worker)

Environment

expo-env-info 2.1.0 environment info:
  System:
    OS: macOS 26.6.2
  Binaries:
    Node: 22.22.2
  npmPackages:
    expo: ^57.0.0 => 57.0.2
    expo-updates: ~57.0.6 => 57.0.6
    react-native: 0.86.0 => 0.86.0
  Expo Workflow: managed

eas-cli/24.7.0
@expo/repack-app 0.10.3 (the version the repack job installed)

Error output

No error. The repack succeeds and the binary carries the wrong build number.

Reproducible demo or steps to reproduce from a blank project

  1. npx create-expo-app, then eas init.

  2. Set "cli": { "appVersionSource": "remote" } in eas.json and add a profile with "autoIncrement": true and "distribution": "internal".

  3. Add a workflow that builds once and then repacks that build:

    jobs:
      build:
        type: build
        params: { platform: android, profile: preview }
      repack:
        needs: [build]
        type: repack
        params:
          build_id: ${{ needs.build.outputs.build_id }}
  4. Run it. EAS shows the repack with the next build number (for example 2).

  5. For iOS (swap platform: ios), unzip the repacked IPA and read CFBundleVersion from Payload/*.app/Info.plist: it's 1. For Android, aapt dump badging app.apk | grep versionCode on the repacked APK should report versionCode='1' by the same code path.

  6. On Android, installing that APK over the full build should then fail with INSTALL_FAILED_VERSION_DOWNGRADE.

Expected: the repacked binary's CFBundleVersion / versionCode is the build number EAS recorded for the repack. That's what autoIncrement promises and what the dashboard shows. Passing the resolved build number (and version) from the job into repackAppIosAsync / repackAppAndroidAsync, instead of relying on the Expo config, would fix both platforms.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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