What Automatic Recovery Protects
Automatic recovery protects an app from repeatedly starting an unhealthy OTA bundle. Native code records each candidate launch before React Native starts. If that launch never becomes healthy, a later cold launch counts it as incomplete.
After the configured number of incomplete attempts, Bundle Drop blocks the failed bundle on that installation and restores a verified, previously healthy compatible OTA bundle. If no eligible previous OTA is available, it uses the embedded bundle shipped with the app binary.
This is separate from dashboard rollback. Native startup protection works offline and does not wait for manual, on-next-launch, or immediate update policy decisions. Manual update control and manual startup health reporting are separate settings.
This read-only walkthrough shows the default three-incomplete-attempt threshold across four distinct cold launches. It loops through both fallback paths: a previously healthy compatible OTA, then the embedded bundle when no previous OTA is eligible.
From incomplete startup to native recovery
Device startup walkthrough- First startup
- Retry once
- Retry again
- Native recovery
Cold launch 4: the threshold is reached. With no eligible previous OTA, native code blocks the failed candidate and selects the embedded bundle shipped in the installed binary.
How It Works
- The app cold-launches an OTA candidate.
- The candidate must reach its configured startup health boundary.
- If it does not, the next distinct cold launch counts the unfinished attempt and retries while below
maxCrashCount. - At the threshold, Bundle Drop blocks the candidate on that installation and selects an eligible healthy fallback.
With the default maxCrashCount: 3, the first candidate launch has no prior incomplete attempts. Three later cold launches with no successful health acknowledgement reach the recovery threshold. Calling the bundle resolver repeatedly within one process does not count as additional launches.
The previous OTA must still pass verification, match the native runtime, and not be locally revoked or blocked. Otherwise, recovery uses the embedded bundle. This does not revoke the failed release for other devices.
Default Behavior
In auto health mode, an OTA candidate becomes healthy after React content appears and the healthyAfterSec delay survives in the same process. A process that exits during the delay leaves the attempt incomplete.
Import-time JavaScript errors, React initialization failures, native crashes, watchdog termination, and process death can all leave incomplete startup attempts. Recovery occurs on a later cold launch; it cannot repair the embedded binary or a failure before Bundle Drop's native bundle resolver executes.
Crashes after startup has been marked healthy do not trigger automatic startup recovery. Use your monitoring tools, fix-forward updates, or dashboard rollback for those failures.
Configuration
Configure startup health in bundle.drop.config.js:
module.exports = {
// ... other config
rollback: {
maxCrashCount: 3, // incomplete attempts before recovery (default: 3)
healthCheckMode: "auto", // "auto" or "manual" (default: "auto")
healthyAfterSec: 0, // auto health delay in seconds (default: 0)
},
};healthyAfterSec only affects auto health mode. A longer delay can catch more early startup failures, but also gives normal process termination more time to leave an incomplete attempt. Keep it short unless you need stricter startup validation.
Setting maxCrashCount to 0 disables launch-health classification, retries, new quarantine, and automatic startup rollback. Integrity, runtime compatibility, already blocked bundles, and known revocations remain enforced.
Startup recovery uses the latest verified revocation information already on the device; it does not wait for a network request before launch. It cannot discover a new dashboard action while offline.
Manual Health Reporting
Use healthCheckMode: "manual" when your app has a reliable readiness point beyond the first rendered screen:
module.exports = {
// ... other config
rollback: {
healthCheckMode: "manual",
maxCrashCount: 3,
},
};import { useEffect } from "react";
import { BundleDrop } from "@gfean/react-native-bundle-drop";
BundleDrop.init({
environment: "production",
policy: "on-next-launch",
});
export function App({ appReady }: { appReady: boolean }) {
useEffect(() => {
// Replace appReady with your own startup readiness condition.
if (!appReady) return;
void BundleDrop.reportHealthy();
}, [appReady]);
return <RootNavigator />;
}Call reportHealthy() only after your critical startup work succeeds. It marks the running OTA startup attempt healthy; matching duplicate calls are harmless, and stale calls from an older attempt are ignored. It is not crash analytics or a replacement for monitoring.
Forgetting to call
reportHealthy()can make a healthy OTA update recover after repeated incomplete attempts on later cold launches. Choose manual health mode only when your app reliably reports readiness.
The policy: "on-next-launch" in this example controls update activation. healthCheckMode: "manual" independently controls startup health reporting; you can use it with any update policy.
After Recovery
Later update checks avoid the bundle that failed on this installation. To deliver a fix, publish a new bundle; do not retry the same failed hash on that device. Other devices are unaffected unless you also revoke the release from the dashboard.
When recovery selects the embedded bundle, getInstalledBundleInfo() returns null. Use observability context to identify the code actually running when attaching release information to crash reports.
Related Docs
- For stopping a release on a channel, see Rollback.
- For download and activation timing, see Update Policies.
- For
reportHealthy(), see Runtime APIs. - For diagnosing unexpected recovery, see Troubleshooting.