Required Reading
Plugin Version
5.2.0 (with Polygon Geofencing add-on)
Mobile operating-system(s)
Device Manufacturer(s) and Model(s)
Samsung Galaxy Tab Active5 Pro
Device operating-systems(s)
Android 16
React Native / Expo version
React Native 0.79.6, Expo SDK 53
What happened?
While driving out of a polygon geofence at ~40 km/h, the plugin fired a correct
EXIT for the polygon, then 5 ms later fired an ENTER for the same polygon,
hit-tested against a location sample that is 2.0 seconds OLDER than the sample that
triggered the EXIT (confirmed via the location.timestamp in each event's HTTP
payload — see below). Immediately after the phantom ENTER, the device exited the
polygon's containing circular geofence, which (per the documented design) ceases
hit-testing for that polygon. Net result: the plugin's final published state for the
polygon is inside, while the device drives away — and since monitoring has
ceased, no corrective EXIT is ever fired. The stale "inside" state persists
indefinitely (hours, in our field logs) until geofences are re-registered.
Sequence (from TSLocationManager log, device-local UTC+8)
12:05:50.026 Odometer update (1 Hz samples streaming, device at ~11 m/s)
12:05:51.027 PolygonGeofencingProcessor fireGeofenceEvent: EXIT area-...fc26d588 ← correct (fix @ 04:05:50.981Z, outside)
12:05:51.032 PolygonGeofencingProcessor fireGeofenceEvent: ENTER area-...fc26d588 ← phantom (fix @ 04:05:48.980Z — OLDER than the EXIT's fix)
12:05:51.035 GeofencingProcessor: EXIT containing geofence of Polygon: area-...fc26d588 ← polygon monitoring ceases
12:05:51.036 💾 ✅ (EXIT event persisted)
12:05:51.038 💾 ✅ (ENTER event persisted)
... (only Odometer updates from here on — no further geofence events despite driving 700 m away from the polygon)
Proof the ENTER used a stale sample (HTTP-synced event payloads, UTC)
Both events were auto-synced via the plugin's HTTP service (persistMode: Geofence).
The recorded location.timestamp/coords of each event:
| Event |
location.timestamp (fix) |
Position (lat, lng) |
Delivered to JS |
| EXIT |
2026-07-07T04:05:50.981Z |
-31.7889402, 115.8193189 |
04:05:51.046Z |
| ENTER |
2026-07-07T04:05:48.980Z |
-31.7889423, 115.8195285 |
04:05:51.070Z |
The ENTER's triggering fix predates the EXIT's fix by 2.001 s and is ~20 m back
along the travel path (inside the polygon). The device's actual movement was
monotonic: inside → outside, i.e. chronologically the correct output is a single
EXIT. Every subsequent 1 Hz sample (visible as Odometer updates) was further
outside the polygon, yet no EXIT followed the phantom ENTER because the containing
circular geofence had been exited and hit-testing stopped.
Note: the ENTER's fix (04:05:48.980) was an internal engine sample — it was never
emitted through onLocation (our JS location stream jumps 04:05:48.0 → 04:05:50.0).
Expected behaviour
- The polygon hit-testing engine should enforce timestamp monotonicity per
geofence — a sample older than the last-evaluated sample should not produce a
state transition (Android's fused provider can deliver samples out of order).
- Alternatively/additionally: when the containing circular geofence EXITs, the
engine should reconcile the polygon state (fire a final EXIT if its last state
is "inside") rather than ceasing monitoring with a stale "inside" state.
Impact
This is a workforce attendance product: the stuck "inside" state means workers
appear on-site hours after leaving. We have reproduced the same signature at two
different polygons on different days (a village-area polygon exit at ~40 km/h, and
a home-area polygon), always: EXIT → phantom ENTER (older fix) → containing-circle
EXIT → no further events.
Reproduction
- Polygon geofence whose edge runs along/near a road.
geofenceModeHighAccuracy: true, disableStopDetection: true, distanceFilter: 10,
device in the active/moving state (~1 Hz samples).
- Drive out across the polygon edge at 40+ km/h, repeatedly. The bug fires on the
pass where an out-of-order sample lands at the transition (probabilistic —
a handful of passes has been enough for us). Signature to look for: EXIT followed
within ms by ENTER whose location.timestamp is older than the EXIT's.
- Very inconsistent to replicate
Plugin Code and/or Config
await BackgroundGeolocation.ready({
desiredAccuracy: BackgroundGeolocation.DesiredAccuracy.High,
distanceFilter: 10,
stopOnStationary: false,
disableStopDetection: true, // permanently-powered vehicle-mounted tablet
isMoving: true,
stationaryRadius: 25,
stopTimeout: 5,
geofenceInitialTriggerEntry: true,
locationAuthorizationRequest: 'Always',
preventSuspend: true,
heartbeatInterval: 15,
geofenceModeHighAccuracy: true,
pausesLocationUpdatesAutomatically: false,
disableElasticity: true,
logLevel: BackgroundGeolocation.LOG_LEVEL_INFO,
url: '', // set post-login via setConfig (bearer auth + endpoint)
autoSync: true, // enabled post-login
batchSync: false,
autoSyncThreshold: 0,
maxRecordsToPersist: 2000,
maxDaysToPersist: 14,
persistMode: BackgroundGeolocation.PersistMode.Geofence,
notification: { /* foreground-service notification */ },
});
// Polygon geofences (36 registered across 7 sites at the time of the incident):
await BackgroundGeolocation.addGeofence({
identifier: 'area-36ac478f-73f8-4a08-b28f-50859aaa3b7b-fc26d588-4de1-4608-8573-3857dbe681f2',
vertices: [ /* [lat, lng] polygon ring */ ],
notifyOnEntry: true,
notifyOnExit: true,
notifyOnDwell: false,
});
Relevant log output
--- Context: device stationary inside polygon since 11:58 (local, UTC+8), then walks to vehicle and drives west along the polygon's edge ---
07-07 12:05:30.241 INFO [ActivityRecognitionService a]
╔═════════════════════════════════════════════
║ Motion Transition Result
╠═════════════════════════════════════════════
╟─ 🔴 EXIT: still
╟─ 🎾 ENTER: in_vehicle
╚═════════════════════════════════════════════
07-07 12:05:30.251 INFO [TSLocationManager a]
║ motionchange LocationResult: 24 (270ms old)
╟─ 📍 Location[fused -31.789110,115.819918 hAcc=6.587 ...], time: 1783397129979
07-07 12:05:30.254 INFO [TSLocationManager requestLocationUpdates] 🎾 Location-services: ON
07-07 12:05:39.020 INFO [Odometer b] ℹ️ Update odometer: 139070.88 (± 736.83m)
07-07 12:05:44.025 INFO [Odometer b] ℹ️ Update odometer: 139082.74 (± 736.85m)
07-07 12:05:46.029 INFO [Odometer b] ℹ️ Update odometer: 139095.01 (± 736.87m)
07-07 12:05:48.025 INFO [Odometer b] ℹ️ Update odometer: 139109.18 (± 736.89m)
07-07 12:05:50.026 INFO [Odometer b] ℹ️ Update odometer: 139126.10 (± 736.91m)
07-07 12:05:51.027 INFO [PolygonGeofencingProcessor fireGeofenceEvent]
╔═════════════════════════════════════════════
║ Geofencing Event: EXIT
╠═════════════════════════════════════════════
╟─ area-36ac478f-73f8-4a08-b28f-50859aaa3b7b-fc26d588-4de1-4608-8573-3857dbe681f2
╚═════════════════════════════════════════════
07-07 12:05:51.032 INFO [PolygonGeofencingProcessor fireGeofenceEvent]
╔═════════════════════════════════════════════
║ Geofencing Event: ENTER
╠═════════════════════════════════════════════
╟─ area-36ac478f-73f8-4a08-b28f-50859aaa3b7b-fc26d588-4de1-4608-8573-3857dbe681f2
╚═════════════════════════════════════════════
07-07 12:05:51.035 INFO [GeofencingProcessor handleGeofencingEvent]
╔═════════════════════════════════════════════
║ Geofencing Event: EXIT
╠═════════════════════════════════════════════
╟─ EXIT containing geofence of Polygon: area-36ac478f-73f8-4a08-b28f-50859aaa3b7b-fc26d588-4de1-4608-8573-3857dbe681f2
╚═════════════════════════════════════════════
07-07 12:05:51.036 INFO [w a] 💾 ✅ 826ae083-0752-4f60-9d98-79c43b8ba19e
07-07 12:05:51.038 INFO [w a] 💾 ✅ 7b7f070b-759d-4574-bbab-3edb6712d512
--- From 12:05:51 onward: Odometer updates only (device driving away from polygon, ~13 m/s). No further geofence events for the remainder of the session. ---
07-07 12:05:51.045 INFO [Odometer b] ℹ️ Update odometer: 139138.99 (± 736.93m)
07-07 12:05:52.017 INFO [Odometer b] ℹ️ Update odometer: 139150.39 (± 736.95m)
07-07 12:05:53.008 INFO [Odometer b] ℹ️ Update odometer: 139161.27 (± 736.97m)
... (odometer continues to 139480+ = ~350m further; no geofence events)
--- HTTP payloads of the two persisted events (via our geofenceTemplate; note location timestamps) ---
EXIT : fix 2026-07-07T04:05:50.981Z @ -31.7889402,115.8193189
ENTER: fix 2026-07-07T04:05:48.980Z @ -31.7889423,115.8195285 <-- 2.0s OLDER than the EXIT's fix
Required Reading
Plugin Version
5.2.0 (with Polygon Geofencing add-on)
Mobile operating-system(s)
Device Manufacturer(s) and Model(s)
Samsung Galaxy Tab Active5 Pro
Device operating-systems(s)
Android 16
React Native / Expo version
React Native 0.79.6, Expo SDK 53
What happened?
While driving out of a polygon geofence at ~40 km/h, the plugin fired a correct
EXIT for the polygon, then 5 ms later fired an ENTER for the same polygon,
hit-tested against a location sample that is 2.0 seconds OLDER than the sample that
triggered the EXIT (confirmed via the
location.timestampin each event's HTTPpayload — see below). Immediately after the phantom ENTER, the device exited the
polygon's containing circular geofence, which (per the documented design) ceases
hit-testing for that polygon. Net result: the plugin's final published state for the
polygon is inside, while the device drives away — and since monitoring has
ceased, no corrective EXIT is ever fired. The stale "inside" state persists
indefinitely (hours, in our field logs) until geofences are re-registered.
Sequence (from TSLocationManager log, device-local UTC+8)
Proof the ENTER used a stale sample (HTTP-synced event payloads, UTC)
Both events were auto-synced via the plugin's HTTP service (
persistMode: Geofence).The recorded
location.timestamp/coords of each event:The ENTER's triggering fix predates the EXIT's fix by 2.001 s and is ~20 m back
along the travel path (inside the polygon). The device's actual movement was
monotonic: inside → outside, i.e. chronologically the correct output is a single
EXIT. Every subsequent 1 Hz sample (visible as Odometer updates) was further
outside the polygon, yet no EXIT followed the phantom ENTER because the containing
circular geofence had been exited and hit-testing stopped.
Note: the ENTER's fix (04:05:48.980) was an internal engine sample — it was never
emitted through
onLocation(our JS location stream jumps 04:05:48.0 → 04:05:50.0).Expected behaviour
geofence — a sample older than the last-evaluated sample should not produce a
state transition (Android's fused provider can deliver samples out of order).
engine should reconcile the polygon state (fire a final EXIT if its last state
is "inside") rather than ceasing monitoring with a stale "inside" state.
Impact
This is a workforce attendance product: the stuck "inside" state means workers
appear on-site hours after leaving. We have reproduced the same signature at two
different polygons on different days (a village-area polygon exit at ~40 km/h, and
a home-area polygon), always: EXIT → phantom ENTER (older fix) → containing-circle
EXIT → no further events.
Reproduction
geofenceModeHighAccuracy: true,disableStopDetection: true,distanceFilter: 10,device in the active/moving state (~1 Hz samples).
pass where an out-of-order sample lands at the transition (probabilistic —
a handful of passes has been enough for us). Signature to look for: EXIT followed
within ms by ENTER whose
location.timestampis older than the EXIT's.Plugin Code and/or Config
Relevant log output