All posts
4 min readBy BetterCodePush

When an OTA update crashes: how BetterCodePush 1.0 recovers on its own

A bad bundle that crashes on launch rolls itself back. One that crashes later can still be fixed remotely, even if its JavaScript never gets far enough to check for updates.

BetterCodePush 1.0ReliabilityRolloutsReact Native

Every OTA system can ship a broken bundle. What matters is what happens next: does the device get stuck on it, or does it find its way back without a store release? BetterCodePush 1.0 handles the two ways a bundle can fail differently.

A bundle that crashes on launch rolls itself back

A new bundle is on probation until it renders its first screen. If the app crashes before then, the next start goes back to the previous bundle, or to the one built into the app, and records the failed bundle as crashed. The device never downloads that bundle again, so it cannot enter a crash loop.

Your app can see this happen and report it:

import { BetterCodePush } from "@bettercodepush/react-native";

export const betterCodePush = BetterCodePush.init({
  // called once the first screen has rendered
  onNotifyAppReady: (result) => {
    if (result.status === "RECOVERED") {
      // report result.fromBundleId -> result.toBundleId to your crash tool
    }
  },
});

export default betterCodePush.wrap({ updateStrategy: "appVersion" })(App);

betterCodePush.getCrashHistory() lists the bundles a device has rejected, and clearCrashHistory() lets it try them again, for example after you have disabled and replaced a release.

A bundle that crashes later can still be fixed

A crash after the first screen is different. The bundle launched, so rolling it back automatically would punish a release for a bug that may have nothing to do with it. But if the crash happens early in every session, the JavaScript update check may never run, and the device would be stuck until the user installs a new store build.

1.0 closes that gap natively. After such a crash, the next launch runs the update check from native code in the background, using the same request the JavaScript check last made. If you have shipped a fix, or disabled the bad release, the device picks it up and applies it on the following launch. No JavaScript from the broken bundle has to run for this to work, and the check only runs after a crash.

Your part: disable fast, then fix

The safety net works best with a quick human response:

# stop serving the bad release; devices fall back to the previous one
npx bettercodepush bundle disable "$RELEASE_ID" --yes

# then deploy the fix as a new release
npx bettercodepush deploy --platform ios --channel production --rollout 10

Pair it with a staged rollout, so a bad release only ever reaches a small share of users, and with bundle signing, so the device only runs bundles you built. If an update is not applying at all, start with this checklist.

Ship your first update

Create an organization and deploy an over-the-air update in minutes. No billing setup required.

Start with BetterCodePush