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
- RN 0.83+ app with New Architecture (default), iOS device on iOS 26
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.
Summary
The static
Mixpanel.init()helper passes 5 arguments to the nativeinitializemethod, which declares 6 arguments (it omitsuseGzipCompression). 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 wrongNSInvocationslots — the module invocation then retains a garbage pointer and the app crashes with a fatalEXC_BAD_ACCESS(KERN_PROTECTION_FAILURE) inobjc_retainthe momentinitis 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
useGzipCompressionwas added to the native signature without updating the static JS helper)useGzipCompression)The mismatch
index.js— static helper passes 5 args:index.js— instance method correctly passes 6:ios/MixpanelReactNative.m— native export declares 6 args + resolver/rejecter:The legacy bridge tolerated the short call; the New Architecture interop layer does not.
Crash stack (symbolicated, from Sentry)
Reproduction
const mp = await Mixpanel.init("<token>", true);→ immediate fatal crashWorkaround
Use the instance API instead of the static helper:
Suggested fix
Add the missing argument in the static helper (and consider forwarding
superProperties/serverURL/useGzipCompressionparams so both APIs stay in sync):A codegen'd TurboModule spec would also catch this class of bug at build time.