All posts
3 min readBy BetterCodePush

Why your OTA update isn't applying: a debugging checklist that actually converges

The deploy was green, the dashboard says released, and users are still on the old bundle. Work through these common causes in order to narrow down where delivery or activation is failing.

TroubleshootingOTAReact NativeDebugging

"The update isn't applying" can mean several different things. Publishing may have succeeded while targeting, download, activation, or runtime compatibility failed on a device. Check the layers in this order; it keeps the investigation observable.

0. First, make the problem observable

Before hunting: log an identifier for the running bundle — a version string or hash — at app startup, and put a visible marker in the update you're testing (change a label; don't rely on subtle behavior). Without these two things you cannot distinguish "not downloaded," "downloaded but not applied," and "applied but I can't tell."

1. The update is waiting for a real restart

Many update clients download before they activate an update. For example, Expo's default strategy checks at cold boot and loads the new update on a later cold boot; see its update-download guidance. Your client may differ. Fully terminate and relaunch the app, then inspect the running bundle identifier before changing anything else. Decide explicitly which updates justify immediate activation versus a later restart.

2. Targeting mismatch: wrong app version or channel

Updates are scoped — to a binary app version and to a channel. The classic misses: you deployed targeting 1.4.0 but the store fleet is mostly on 1.3.x; the device is running a TestFlight build wired to staging while you deployed to production; or the binary's version string doesn't exactly match what you targeted. Read the device's actual version and channel off a debug screen or log, then compare against the release. Character by character; "1.4" and "1.4.0" are different strings.

3. You're not in the rollout cohort

A release at 10% may not be offered to a given test device. Check the rollout rules, cohort assignment, and device identity before questioning reality.

4. Runtime incompatibility — rejected or crash-reverted

A bundle built against an incompatible React Native or Hermes runtime may fail to load or crash after activation. Client handling — including whether it falls back — is implementation-specific. Crash logs from the first launch after update are the tell. The mechanics and prevention rules are in the Hermes bytecode compatibility post.

5. Classic CodePush on the New Architecture

The archived react-native-code-pushrepository says it does not support the New Architecture and directs React Native 0.76+ users to opt out if they continue using it. If your app runs on the New Architecture, treat this as a client-compatibility problem and plan a migration rather than endlessly tuning configuration.

6. The release itself is disabled, superseded, or stale-cached

Check the server side with fresh eyes: is the release actually enabled? Did a teammate ship a newer release that supersedes yours? Then check your provider's cache and propagation behavior. Do not infer a universal propagation window from one device test.

7. The update applied — and reverted later

The subtle one. The bundle applied, then the client's failure recovery rejected or reverted it. This looks identical to "never applied" unless your startup log includes the bundle identifier over time. If you find this pattern, investigate the update's startup behavior and the client's recovery policy.

Make it a fleet question, not a device question

Once the immediate mystery is solved, zoom out: what fraction of active devices are on the latest compatible bundle over a window that fits your app's usage pattern? Compare that adoption trend with rollout eligibility, download failures, activation failures, and crashes. Alert on the signals that map to your client's actual update lifecycle.

Ship your first update

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

Start with BetterCodePush