Skip to content

Static Mixpanel.init() passes 5 args to the 6-arg native initialize — fatal EXC_BAD_ACCESS at boot on RN New Architecture (iOS) #433

Description

@tomas-reuser

Summary

The static Mixpanel.init() helper passes 5 arguments to the native initialize method, which declares 6 arguments (it omits useGzipCompression). On React Native's New Architecture, where legacy modules run through the TurboModule interop layer, the missing argument shifts the promise resolve/reject blocks into the wrong NSInvocation slots — the module invocation then retains a garbage pointer and the app crashes with a fatal EXC_BAD_ACCESS (KERN_PROTECTION_FAILURE) in objc_retain the moment init is called (for most apps: at boot).

The instance API is unaffected (new Mixpanel(...) + mixpanel.init() passes all 6 arguments), which is likely why this hasn't been widely reported — only apps using the static helper crash.

Affected versions

  • mixpanel-react-native 3.3.0 and 3.4.0 (both verified; the bug was presumably introduced when useGzipCompression was added to the native signature without updating the static JS helper)
  • React Native 0.83.6 (Expo SDK 55), New Architecture (module runs via interop — the package has no codegen spec)
  • iOS 26.5, reproduced on device (iPhone 16 Pro Max); crashes identically in Debug and Release
  • Version 3.0.9 is fine (predates useGzipCompression)

The mismatch

index.js — static helper passes 5 args:

static async init(token, trackAutomaticEvents, optOutTrackingDefault = DEFAULT_OPT_OUT) {
  await MixpanelReactNative.initialize(
    token,
    trackAutomaticEvents,
    optOutTrackingDefault,
    Helper.getMetaData(),
    "https://api.mixpanel.com"
    // <-- useGzipCompression missing
  );
  ...
}

index.js — instance method correctly passes 6:

async init(optOutTrackingDefault = DEFAULT_OPT_OUT, superProperties = {}, serverURL = "...", useGzipCompression = false) {
  await this.mixpanelImpl.initialize(
    this.token, this.trackAutomaticEvents, optOutTrackingDefault,
    {...Helper.getMetaData(), ...superProperties}, serverURL, useGzipCompression
  );
}

ios/MixpanelReactNative.m — native export declares 6 args + resolver/rejecter:

RCT_EXTERN_METHOD(initialize:(NSString *)token trackAutomaticEvents:(BOOL)trackAutomaticEvents optOutTrackingByDefault:(BOOL)optOutTrackingByDefault properties:(NSDictionary *)properties serverURL:(NSString *)serverURL useGzipCompression:(BOOL)useGzipCompression resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)

The legacy bridge tolerated the short call; the New Architecture interop layer does not.

Crash stack (symbolicated, from Sentry)

EXC_BAD_ACCESS: Exception 1, Code 2 — KERN_PROTECTION_FAILURE
  at objc_retain
  at facebook::react::ObjCTurboModule::createPromise::lambda::operator()
  at MixpanelReactNative.initialize (MixpanelReactNative.swift:31)
  at __invoking___
  at -[NSInvocation invoke]
  at -[NSInvocation invokeWithTarget:]
  at facebook::react::ObjCTurboModule::performMethodInvocation

Reproduction

  1. RN 0.83+ app with New Architecture (default), iOS device on iOS 26
  2. const mp = await Mixpanel.init("<token>", true); → immediate fatal crash

Workaround

Use the instance API instead of the static helper:

const mixpanel = new Mixpanel("<token>", true);
await mixpanel.init(); // passes all 6 args — no crash

Suggested fix

Add the missing argument in the static helper (and consider forwarding superProperties/serverURL/useGzipCompression params so both APIs stay in sync):

await MixpanelReactNative.initialize(
  token, trackAutomaticEvents, optOutTrackingDefault,
  Helper.getMetaData(), "https://api.mixpanel.com", false
);

A codegen'd TurboModule spec would also catch this class of bug at build time.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions