# Troubleshooting — Bundle Drop Docs

> Troubleshoot common Bundle Drop OTA issues — updates not applying, runtime version mismatches, and delivery problems.

Canonical: https://bundledrop.app/docs/troubleshooting

## How to Use This Page

Most Bundle Drop issues fall into one of a few buckets: setup, uploading, delivery, or crash protection. Find the symptom below, confirm the cause, and apply the fix.

## Setup and Build

### Bare Metro can't resolve `bundle-drop-config`

Your `metro.config.js` may be missing the Bundle Drop wrapper, or a custom export may bypass it. Wrap your final existing config so Bundle Drop can prepare the runtime configuration as well as the resolver:

**metro.config.js**

```js
const { getDefaultConfig, mergeConfig } = require("@react-native/metro-config");
const { withBundleDrop } = require("@gfean/react-native-bundle-drop/metro");

// Preserve your app's existing Metro customizations here.
const config = mergeConfig(getDefaultConfig(__dirname), {});

module.exports = withBundleDrop(config, { projectRoot: __dirname });
```

Rerun `npx bundle-drop init --project-type bare`, review the proposed Metro change, and then run `npx bundle-drop doctor`. Expo projects should preserve `expo/metro-config` through the Bundle Drop wrapper; see [Expo](/docs/expo#install-and-set-up).

### Bundling fails with `EMFILE`

The OS file-descriptor limit is too low for the Metro bundle. Raise it and retry:

**Terminal**

```bash
ulimit -n 8192
npx bundle-drop upload android --version 1.2.3 --channel develop
```

## Uploading

### "Channel not found"

The channel you passed to `--channel` does not exist in the project. Channels are created in the dashboard — upload never auto-creates them. New projects start with a channel named `develop`. Check the channel name (case matters) or create it first. `--channel` is always required. See [Channels](/docs/channels).

### "bundle-manifest.json is missing runtimeVersion"

The runtime version was not resolved during bundling. Check the selected compatibility mode in `bundle.drop.config.js`: manual mode needs a platform value, while fingerprint mode must successfully calculate the native identity. There is no `--runtimeVersion` flag — the value comes from the config-derived manifest. See [Runtime Version](/docs/runtime-version).

### "This bundle already exists — promote it instead"

You uploaded a bundle that is byte-for-byte identical to one already in the project. The error response (and the dashboard) shows which channels the bundle already lives on. Instead of re-uploading, promote the existing bundle to the target channel from its Bundles page. See [Channels](/docs/channels#promoting-bundles-to-another-channel).

### Authentication fails in CI

CI does not have an interactive `bundle-drop login` session. Pass a Personal Access Token with `--token` and keep it in your CI secret store. See [CI/CD](/docs/ci-cd).

## Update Not Delivered

When `checkForUpdate()` returns `NOOP` (or the app just never updates), work through these in order:

- **Runtime version mismatch.** Bundle Drop only delivers a bundle on the same runtime version as the installed binary. On a channel *without* managed publishing, a mismatch surfaces as a `NO_COMPATIBLE_BUNDLE` reason carrying `requestedRuntimeVersion` and `latestRuntimeVersionOnChannel`; on a managed-publishing channel it simply looks like no eligible bundle. See [Runtime Version](/docs/runtime-version).
- **Managed publishing with no active rollout.** If the channel has [Managed Publishing](/docs/managed-publishing) enabled, only bundles in an active or completed rollout are eligible — a **paused** rollout makes its bundle ineligible too. Start, resume, or advance a rollout for the bundle.
- **Rollout percentage excludes this install.** Staged rollouts are bucketed deterministically per install, so a device below the current percentage will not receive the update until you raise it. See [Staged Rollouts](/docs/staged-rollouts).
- **Targeting mismatch.** If the rollout has targeting rules, the device's user properties must match at least one rule. Confirm the properties are set in app code and defined in the dashboard. See [Targeted Rollout](/docs/targeted-rollout-properties).
- **Wrong channel.** Confirm the app's active `channelName` matches the channel you uploaded to.

### Update downloaded but not applied

With `policy: "on-next-launch"`, the bundle is staged and applied on the next launch — this is expected. Restart the app, or call `applyUpdate()` yourself. Check `getUpdateState()` for `pendingApply`. See [Update Policies](/docs/update-policies).

## Fingerprint Mismatch

Inspect the current platform with `npx bundle-drop fingerprint --platform ios` or `--platform android`, and compare a saved report when available. Check changes to native dependencies, configuration, lockfiles, and any explicit platform runtime boundary. A tooling or JavaScript change inside an autolinked native dependency can also change its package digest.

If the source legitimately belongs to a new runtime, build that native binary before expecting delivery. If the upload should match an existing binary, restore the matching inputs rather than overriding its hash. Changing a config file or running an inspection command cannot rewrite the identity inside an installed app. See [Native Fingerprinting](/docs/native-fingerprinting#keep-builds-and-uploads-aligned).

Also verify declared build variants and [explicit sources](/docs/native-fingerprinting#coverage-and-custom-build-inputs). A custom native change outside collector coverage may need a source declaration or a deliberate hybrid boundary change, even if the existing hash did not change. Avoid weakening ignore rules just to make mismatched inputs appear compatible.

## Crash Protection and Rollback

### An update was rolled back automatically

The bundle repeatedly launched without being marked healthy, so on-device startup protection recovered to a healthy fallback. The failed bundle is blocked on that device and reported as `skippedFailedBundle` on later checks. Publish a new bundle to ship a fix — do not retry the same failed update on that device. See [Automatic Recovery](/docs/automatic-recovery).

### A healthy update keeps rolling back

You are likely in manual health mode (`healthCheckMode: "manual"`) without calling `reportHealthy()`. Each later distinct launch then classifies the previous unfinished attempt. Call `BundleDrop.reportHealthy()` at your startup readiness point, or switch back to `auto`. See [Manual Health Reporting](/docs/automatic-recovery#manual-health-reporting).

## Error Tracking

### Stack traces from OTA bundles are not symbolicated

Source maps were not uploaded, or the `dist`/`release` identifier does not match the bundle. Upload with `--sourcemap`, and use the bundle's `hash` (from `getObservabilityContext().dist`) as the dist identifier in both CI and runtime config. See [Observability](/docs/observability).

## Related Docs

- For setup, see [Installation](/docs/installation).
- For publishing, see [Uploading](/docs/uploading).
- For delivery gating, see [Managed Publishing](/docs/managed-publishing) and [Staged Rollouts](/docs/staged-rollouts).
- For crash protection, see [Rollback](/docs/rollback).
- For error tracking, see [Observability](/docs/observability).
