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
-
npx create-expo-app, then eas init.
-
Set "cli": { "appVersionSource": "remote" } in eas.json and add a profile with "autoIncrement": true and "distribution": "internal".
-
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 }}
-
Run it. EAS shows the repack with the next build number (for example 2).
-
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.
-
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.
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", atype: repackworkflow 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):appBuildVersionCFBundleVersionin the IPAOn Android this blocks installs. A repacked APK with
versionCode1 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-toolsin this repo) passes@expo/repack-appno version information (packages/build-tools/src/steps/functions/repack.ts:repackAppIosAsyncandrepackAppAndroidAsyncget the project root, source app, signing options and env).@expo/repack-app0.10.3 then reads the build number from the project's Expo config and falls back to 1:buildNumber = config.ios?.buildNumber || "1", written toCFBundleVersionin the app's and every app extension'sInfo.plist.versionCode = Number(config.android?.versionCode ?? 1), written toandroid:versionCodein the manifest.Under a remote version source the Expo config has no
ios.buildNumberorandroid.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-app0.10.3 source; we didn't ship an Android repack, because the downgrade would have blocked installs.Managed or bare?
Managed (CNG,
expo prebuildon the worker)Environment
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
npx create-expo-app, theneas init.Set
"cli": { "appVersionSource": "remote" }ineas.jsonand add a profile with"autoIncrement": trueand"distribution": "internal".Add a workflow that builds once and then repacks that build:
Run it. EAS shows the repack with the next build number (for example 2).
For iOS (swap
platform: ios), unzip the repacked IPA and readCFBundleVersionfromPayload/*.app/Info.plist: it's1. For Android,aapt dump badging app.apk | grep versionCodeon the repacked APK should reportversionCode='1'by the same code path.On Android, installing that APK over the full build should then fail with
INSTALL_FAILED_VERSION_DOWNGRADE.Expected: the repacked binary's
CFBundleVersion/versionCodeis 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 intorepackAppIosAsync/repackAppAndroidAsync, instead of relying on the Expo config, would fix both platforms.