Anthony Wentzel

The React Native bridge promise that never resolved

A one-line React Native bridge bug in react-native-background-geolocation from 2018, and the rule I still build agents around: fail loudly, never hang.

From 2018 · 2 min read · Published September 21, 2026

In 2018 we were building rider apps at Freebird on React Native, leaning heavily on react-native-background-geolocation from Transistor Software. It's an excellent library, and it's the kind of dependency where the JavaScript API is a thin layer over the React Native bridge and a lot of native code.

We hit a bug where calling removeListeners() on iOS would simply never finish. No error, no crash. Any code that awaited it just stopped.

The cause: a bridge promise with no resolve

React Native bridge methods hand native code a pair of callbacks, resolve and reject. If native code never calls either, the JavaScript promise stays pending forever. That's what was happening: the iOS implementation cleaned up correctly but never called its success callback.

The fix was one line:

ios/RNBackgroundGeolocation/RNBackgroundGeolocation.m
@synchronized(listeners) { [listeners removeAllObjects]; }
[locationManager removeListeners];
success(); 

I opened a PR with it (#557), along with an earlier one (#492) that updated the Xcode project for newer toolchains. Neither was merged. We carried the patch locally and moved on.

Why I still think about it

A hang is the worst failure mode there is. An error tells you something went wrong and roughly where. A hang tells you nothing, and it tends to show up as a different bug somewhere downstream: a spinner that never stops, a screen that won't unmount, a background task that quietly never runs again.

Every native bridge method should resolve or reject on every code path. I now carry that into everything asynchronous we build, including AI agents:

  • Every agent run has a deadline.
  • Every tool call has a budget.
  • Every result says whether a limit was hit.

It's the same rule, eight years later. If something can't finish, it should say so.


Related: Wrapping a native SDK in a React Native native module · An offline upload queue that never stalled, and never complained