---
title: Issues & Crashes
---
# Issues & Crashes

The Capture SDK can automatically detect, capture, and upload issues and errors that affect your app.

!!! note ""
    See the [Product > Issues Overview](../../product/issues/overview.md#features) to learn more about how to navigate this information in the product.

## Setup

Fatal issue reporting is **enabled by default** in the Capture SDK since 0.18.7 version but can be disabled as shown below.

## Issue Report Callback

<img alt="Capture SDK Support" src="https://img.shields.io/badge/Android-0.22.12-006C9C">
<img alt="Capture SDK Support" src="https://img.shields.io/badge/iOS-0.22.12-006C9C">
<img alt="Capture SDK Support" src="https://img.shields.io/badge/React%20Native-0.11.13-006C9C">

You can register a callback to receive issue report metadata right before the SDK sends a report.

!!! example "Experimental"
    This callback API is experimental.

Use `issueCallbackConfiguration` in `Configuration`:

=== "Android (Kotlin)"

    ```kotlin
    import io.bitdrift.capture.Configuration
    import io.bitdrift.capture.Capture
    import io.bitdrift.capture.experimental.ExperimentalBitdriftApi
    import io.bitdrift.capture.reports.IssueCallbackConfiguration
    import io.bitdrift.capture.reports.IssueReportCallback
    import io.bitdrift.capture.reports.Report

    @OptIn(ExperimentalBitdriftApi::class)
    val config = Configuration(
        enableFatalIssueReporting = true,
        issueCallbackConfiguration = IssueCallbackConfiguration(
            executor = java.util.concurrent.Executors.newSingleThreadExecutor(),
            issueReportCallback = IssueReportCallback { report ->
                // TODO: Add your custom handling for report metadata.
            },
        ),
    )
    ```

=== "iOS (Swift)"

    ```swift
    import Capture
    import Foundation

    final class CustomerIssueReportCallback: NSObject, IssueReportCallback {
      func onBeforeReportSend(report: IssueReport) {
        // TODO: Add your custom handling for report metadata.
      }
    }

    let config = Capture.Configuration(
      enableFatalIssueReporting: true,
      issueCallbackConfiguration: IssueCallbackConfiguration(
        callbackQueue: DispatchQueue(label: "customer-issue-report-callback"),
        issueReportCallback: CustomerIssueReportCallback()
      )
    )
    ```

=== "React Native (JavaScript)"

    ```javascript
    import { init, SessionStrategy } from '@bitdrift/react-native';

    init('<api-key>', SessionStrategy.Activity, {
      crashReporting: {
        UNSTABLE_onBeforeReportSend: (report) => {
          // Add your custom handling for report metadata.
          // report contains: reportType, reason, details, sessionId, fields
        },
      },
    });
    ```

`Report` includes:

- `reportType`: High-level issue type (for example `ANR`, `Native Crash`, `Crash`).
- `reason`: Primary issue identifier (for example exception class or signal name).
- `details`: Additional issue details (for example exception message).
- `sessionId`: bitdrift session ID associated with the report (`sessionID` on iOS).
- `fields`: Additional report fields available at callback time.

## Previous Run Info

<img alt="Capture SDK Support" src="https://img.shields.io/badge/Android-0.22.13-006C9C">
<img alt="Capture SDK Support" src="https://img.shields.io/badge/iOS-0.22.13-006C9C">
<img alt="Capture SDK Support" src="https://img.shields.io/badge/React%20Native-0.11.14-006C9C">

You can query whether the app fatally terminated in the previous run.

!!! example "Experimental"
    This API is experimental.

=== "Android (Kotlin)"

    ```kotlin
    import io.bitdrift.capture.Capture
    import io.bitdrift.capture.experimental.ExperimentalBitdriftApi

    @OptIn(ExperimentalBitdriftApi::class)
    val previousRunInfo = Capture.Logger.getPreviousRunInfo()

    // previousRunInfo?.hasFatallyTerminated
    // previousRunInfo?.terminationReason
    ```

=== "iOS (Swift)"

    ```swift
    import Capture

    let previousRunInfo = Logger.previousRunInfo

    // previousRunInfo?.hasFatallyTerminated
    ```

=== "iOS (Objective-C)"

    ```objective-c
    #import <Capture/Capture.h>

    NSDictionary *previousRunInfo = [CAPLogger previousRunInfo];

    // previousRunInfo[@"hasFatallyTerminated"]
    ```

=== "React Native (JavaScript)"

    ```javascript
    import { getPreviousRunInfo } from '@bitdrift/react-native';

    const previousRunInfo = getPreviousRunInfo();

    // previousRunInfo?.hasFatallyTerminated
    // previousRunInfo?.terminationReason
    ```

- Call this API after `Logger.start()` / `init()`.
- On Android below API 30, the only fatal termination reported is JVM crash.
- On Android API 30, native crashes are reported as a fatal previous-run reason but do not trigger `onBeforeSend`. `onBeforeSend` for native crashes is available on API 31+.

## Disable Issue Reporting

If you wish to disable automatic fatal issue reporting, you can set `enableFatalIssueReporting = false` in Configuration [`Logger.start()`](./configuration.md).

=== "Android (Kotlin)"

    ```kotlin
    Logger.start(
      apiKey = "<your-api-key>",
      sessionStrategy = SessionStrategy.Fixed,
      configuration = Configuration(enableFatalIssueReporting = false),
    )
    ```

=== "Android (Java)"

    ```java
    boolean enableFatalIssueReporting = false;
    Logger.start(
      new Configuration(
          new SessionReplayConfiguration(),
          new SessionStrategy.Fixed(),
          enableFatalIssueReporting);
    );
    ```

=== "iOS (Swift)"

    ```swift
    Logger.start(
      withAPIKey: "<your-api-key>",
      sessionStrategy: .fixed(),
      configuration: Capture.Configuration(enableFatalIssueReporting: false),
    )
    ```

=== "iOS (Objective-C)"

    ```objective-c
    #import <Capture/Capture.h>

    CAPConfiguration *config = [[CAPConfiguration alloc]
      initWithEnableFatalIssueReporting:NO
      enableURLSessionIntegration:YES];

    [CAPLogger
      startWithAPIKey:@"your-api-key"
      sessionStrategy: [CAPSessionStrategy fixed]
      configuration:config];
    ```
    !!! note
        Issue reports on iOS are uploaded only for actual devices and not simulators

=== "React Native"

    ```javascript
    import { init, SessionStrategy } from '@bitdrift/react-native';

    init('<api-key>', SessionStrategy.Activity, {
      crashReporting: {
        enableNativeFatalIssues: false,
      }
    });
    ```

## React Native JavaScript Support

The React Native SDK supports capturing global JavaScript errors in addition to platform crashes. This feature is experimental and can be enabled via the `UNSTABLE_enableJsErrors` configuration flag.

!!! info "Supported Configuration"
    JavaScript error reporting is currently supported on Expo builds using the **New Architecture** and **Hermes** engine.

### Configuration

```javascript
import { init, SessionStrategy } from '@bitdrift/react-native';

init('<api-key>', SessionStrategy.Activity, {
  crashReporting: {
    enableNativeFatalIssues: true,
    UNSTABLE_enableJsErrors: true,
  }
});
```

- `enableNativeFatalIssues`: When `true`, enables reporting of native issues including crashes, ANRs (Application Not Responding), and other critical errors. Defaults to `true`.
- `UNSTABLE_enableJsErrors`: When `true`, enables reporting of JavaScript errors via React Native's global error handler. Captures unhandled exceptions with stack traces. Defaults to `false`.

!!! example "Experimental"
    The `UNSTABLE_enableJsErrors` flag is experimental. This feature is under active development and may change in future releases.

## Behavior

### First Install Launch
Crash detection will not be enabled on fresh installations or after clearing app data/cache to allow remote configuration control. You may need to relaunch the app once for crash reporting to become active.

### Session Uploads
Fatal issue reports and their session information (logs, etc) are automatically uploaded and do not require a [Record Session](../../product/workflows/actions.md#record-session) to be running in a Workflow.

### Android

#### Platform Support

| Android version | JVM crashes | Native (NDK) crashes | ANRs |
| --- | --- | --- | --- |
| Android 12+ (API level 31+) | Full | Full | Full |
| Android 11 (API level 30) | Full | Partial (no stack traces) | Full |
| Android 6-10 (API level 23-29) | Full | None | None |

Native crash detection on Android 11+ uses [`ApplicationExitInfo`](https://developer.android.com/reference/android/app/ApplicationExitInfo). On API level 30, Android reports the fatal native exit but does not provide the tombstone data needed to reconstruct a stack trace, so reports include the signal and exit metadata but not frames. Full native stack traces are available on API level 31+.

#### Native Crash Rationale

On Android 11, bitdrift intentionally relies on the platform-native `ApplicationExitInfo` path instead of shipping a separate native crash-capture library just to recover stack traces on older OS versions.

This is an intentional tradeoff. Shipping a separate native crash-capture library would add size, startup cost, and memory overhead for all customers in order to recover stack traces for Android 11 only. In return, bitdrift keeps the platform-native tombstone path, which on Android 12+ provides higher quality crash data than custom signal-handler-based integrations typically can: stack traces for all threads, native frames alongside de-obfuscated JVM frames, and broader OEM symbol support. Native crash reports also stay aligned with Android's process-exit reporting and include the rest of the fatal issue context in bitdrift, such as crash reason, device and app metadata, feature flags, custom fields, and the session timeline.

If a native crash occurs on Android 11, bitdrift still detects and uploads the fatal issue so it can be triaged, but the report will not include a stack trace. This is the expected behavior for API level 30.

#### R8/ProGuard Settings

To preserve line numbers and source file names for stack traces, make sure this rule is added to your app's ProGuard configuration:

`-keepattributes SourceFile,LineNumberTable`

!!! note
    - `SourceFile`: This attribute is kept by default since Android Gradle Plugin (AGP) 8.2, and only needs to be explicitly kept in apps using earlier versions of AGP.
    - `LineNumberTable`: This attribute is kept by default since Android Gradle Plugin (AGP) 8.6, and only needs to be explicitly kept in apps using earlier versions of AGP.

### iOS

#### Platform Support
- Fatal issue reports are uploaded only for physical devices and not simulators.

## Uploading Debug Information Files

To generate human readable stack traces, bitdrift needs your apps' debug information files.

### bd cli tool

Our command-line tool makes it easy to upload debug information files to bitdrift.

#### Installation

##### Homebrew (macOS)

To install the CLI directly on your Mac, use Homebrew:

```sh
brew tap bitdriftlabs/bd
brew install bd
```

##### Direct Download

Direct downloads for other platforms are also available, which are especially useful for integrating into your CI or release process. You can download the latest binaries directly from:

=== "macOS (Universal)"
    <https://dl.bitdrift.io/bd-cli/latest/bd-cli-mac-universal-apple-darwin.tar.gz/bd>
=== "macOS (arm64)"
    <https://dl.bitdrift.io/bd-cli/latest/bd-cli-mac-arm64.tar.gz/bd>
=== "macOS (x86_64)"
    <https://dl.bitdrift.io/bd-cli/latest/bd-cli-mac-x86_64.tar.gz/bd>
=== "Linux (x86_64)"
    <https://dl.bitdrift.io/bd-cli/latest/bd-cli-linux-x86_64.tar.gz/bd>

!!! info
    You can download a specific version of the bd-cli by replacen `latest` above with the version number, e.g. `0.1.36`

#### Usage

=== "Proguard Mappings (Android)"
    ```sh
    bd debug-files upload-proguard
      --api-key <API_KEY>
      --app-id <APP_ID>
      --app-version <APP_VERSION>
      --version-code <VERSION_CODE>
      <PROGUARD_MAPPING_FILE>
    ```
=== "Native Symbols (iOS / Android)"
    ```sh
    bd debug-files upload
      --api-key <API_KEY>
      <FILE_OR_DIRECTORY> # MACH-O, ELF
    ```
=== "Sourcemaps (React Native)"
    ```sh
    bd debug-files upload-source-map \
      --source-map <SOURCEMAP_FILE> \
      --bundle <BUNDLE_FILE> \
      --api-key <API_KEY>
    ```

    Example:
    ```sh
    bd debug-files upload-source-map \
      --source-map ./android/app/build/generated/sourcemaps/react/release/index.android.bundle.map \
      --bundle ./android/app/build/generated/assets/createBundleReleaseJsAndAssets/index.android.bundle \
      --api-key $BD_API_KEY
    ```

!!! note
    For BYOC deployments use `--base-domain` to target your deployment.

### Platform Tools

Bitdrift supports build-time integration to upload the required Debug Information Files easily.

#### Gradle Plugin (Android)

<img alt="Capture SDK Support" src="https://img.shields.io/badge/Android-0.22.3-006C9C">

Debug information files (proguard mappings, native symbols) can be located and uploaded via the Capture Gradle Plugin.

!!! info
    The following requires the usage of the **Capture Gradle Plugin**, learn how to configure it in [Android Quickstart Guide](../quickstart.md#gradle-plugin).

After a project sync, the Capture Gradle Plugin will automatically generate the `bdUpload+` gradle tasks under the `Upload` group. You can then call them after succesfully assembling a release build (via `assembleRelease`):

- `bdUploadMapping` - Locate and upload only mapping files to Bitdrift
- `bdUploadSymbols` - Locate and upload only symbols to Bitdrift
- `bdUploadSourceMap` - Locate and upload only sourcemap files to Bitdrift
- `bdUpload` - Locate and upload all mapping files, symbols, and sourcemaps to Bitdrift (when applicable)

??? warning "You must set an ENV variable named `API_KEY` to call these tasks"
    e.g.
    ```bash
    API_KEY=MY-API-KEY ./gradlew bdUploadMapping
    ```
    or:
    ```bash
    export API_KEY=MY-API-KEY
    ./gradlew bdUploadMapping
    ```


#### Xcode Build Phase Script (iOS)

In the "Build Phases" tab of your Xcode build target, add a run script phase (click the "+" symbol in the top-left, then select "New Run Script Phase"), and use the following script:

???+ note "Build Script"
    ```bash
    ########################################
    # Configuration
    ########################################

    UPLOAD_ON_CONFIGURATIONS=(
        # Debug
        Release
    )

    ########################################
    # Script
    ########################################
    {% raw %}
    set -eu -o pipefail

    if [[ ! " ${UPLOAD_ON_CONFIGURATIONS[*]} " =~ [[:space:]]${CONFIGURATION}[[:space:]] ]]; then
        echo "This build step is not configured to upload symbols when using build configuration \"$CONFIGURATION\", so doing nothing."
        exit 0
    fi

    if [[ "$(uname -m)" == arm64 ]]; then
        BD_ARCH=arm64
    else
        BD_ARCH=x86_64
    fi
    BD_URL="https://dl.bitdrift.io/bd-cli/latest/bd-cli-mac-${BD_ARCH}.tar.gz/bd"
    BD="$TEMP_DIR/bd"

    dsym_files=()
    if [ $ENABLE_USER_SCRIPT_SANDBOXING == "YES" ]; then
        if [ $SCRIPT_INPUT_FILE_COUNT -le 0 ]; then
            echo 'ENABLE_USER_SCRIPT_SANDBOXING is enabled. All dSYMs must be declared in the "Input Files" section of this run script when sandboxing is enabled.'
            echo 'Please either disable ENABLE_USER_SCRIPT_SANDBOXING, or add the following into the "Input Files" section of this run script:'
            echo '    ${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}'
            exit 1
        fi

        for ((n=0;n<$SCRIPT_INPUT_FILE_COUNT;n++)); do
            declare name=SCRIPT_INPUT_FILE_${n}
            if [ -f "${!name}" ]; then
                dsym_files+=("${!name}")
            fi
        done
    else
        dsym_files+=("${DWARF_DSYM_FOLDER_PATH}")
    fi

    if [ ${#dsym_files[@]} -eq 0 ]; then
        echo "Did not find any dSYM files to upload to Bitdrift. By default, Debug builds don't include dSYM files."
        echo "If you want to generate dSYM files in a debug build, modify your 'Debug Information Format' build setting and set it to 'DWARF with dSYM File'."
        exit 0
    fi

    if [ -z ${API_KEY+x} ]; then
        echo "Bitdrift requires the 'API_KEY' environment value to be set."
        echo "Please set API_KEY to your Bitdrift API key value, then launch xcode or xcodebuild from that environment."
        exit 1
    fi

    if [ ! -f "$BD" ]; then
        curl -fSL "$BD_URL" -o "$BD"
        curl -fSL "$BD_URL.sha256" -o "$BD.sha256" || {
            echo "ERROR: Failed to download bd-cli checksum from $BD_URL.sha256"
            rm -f "$BD"
            exit 1
        }

        EXPECTED_CHECKSUM=$(cat "$BD.sha256")
        ACTUAL_CHECKSUM=$(shasum -a 256 "$BD" | awk '{print $1}')

        if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]; then
            echo "ERROR: Checksum verification failed for bd-cli binary"
            echo "Expected: $EXPECTED_CHECKSUM"
            echo "Actual:   $ACTUAL_CHECKSUM"
            rm -f "$BD" "$BD.sha256"
            exit 1
        fi

        chmod a+x "$BD"
        rm -f "$BD.sha256"
    fi

    for path in "${dsym_files[@]}"; do
        echo "Uploading to Bitdrift: $path"
        "$BD" debug-files upload --api-key="$API_KEY" "$path"
    done
    ```
    {% endraw %}

!!! info
    You will need to set the `API_KEY` environment variable to your Bitdrift API key, and run your build under that environment

!!! info
    If you have script sandboxing enabled on your build, you'll need to expose the symbol files so that the script can see them. This can be done by adding an entry with `${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}` to the "Input Files" section of the run script build phase.

!!! note
    For BYOC deployments use `--base-domain` to target your deployment.

### Verifying Uploads & Symbolication Timing

There is currently no UI in the bitdrift dashboard to confirm that debug information files have been uploaded successfully. A successful exit code from the `bd` CLI (or Gradle task) indicates that the upload was accepted.

Once uploaded, new crash reports will be symbolicated within approximately one minute. Note that uploading debug information files does **not** retroactively re-symbolicate previously captured crash reports.
