Skip to content

[Bug]: Polygon geofence fires phantom ENTER from stale (out-of-order) location sample immediately after a valid EXIT; polygon monitoring then ceases, leaving geofence state stuck "inside" #2623

Description

@DomJamo

Required Reading

  • Confirmed

Plugin Version

5.2.0 (with Polygon Geofencing add-on)

Mobile operating-system(s)

  • iOS
  • Android

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

  1. 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).
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions