transistorsoft / transistorsoft/react-native-background-geolocation
[Bug]: iOS: SDK wedges in Stationary with no stationary region (radius 0.0) after a failed motionchange fetch at stopTimeout - promotion vetoed for ~18 h
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 2.9k
- Forks
- 446
- PR merge metrics
- No merged PRs in 30d
Description
Required Reading
- Confirmed
Plugin Version
5.2.0 (bug observed on 5.2.0 / TSLocationManager build 4201; since upgraded to 5.4.0 - see body)
Mobile operating-system(s)
- iOS
- Android
Device Manufacturer(s) and Model(s)
iPhone 15 Pro
Device operating-systems(s)
iOS 26.3.1
React Native / Expo version
0.83.6, Expo SDK 55
What happened?
The device stopped in a GPS-denied spot (arrived home via an underground rail leg; phone indoors). When stopTimeout fired, the single-location request for the motionchange position failed with kCLErrorDomain Code=0. The SDK then fell back to significant-location-changes and entered the Stationary state without ever arming a stationary region.
From that moment, every subsequent promotion evaluation — including SLC wakes and motion-activity hits over the following ~18 hours of real journeys — was vetoed by the stationary-radius check comparing against a radius of 0.0 (timeline in the log output below; the same "Δd: 0.0, radius: 0.0" veto repeats 40 times across ~18.4 h — through a full morning of walking + rail + driving — until a manual app open at 11:46 UTC the next day re-armed the region and tracking resumed mid-journey).
A healthy stop on the same device logs radius: 150.0 on those checks; the zero-radius state appears only after the failed motionchange fetch. On a second device (iPhone 15 Pro Max, iOS 26.5, identical config) that never hits a GPS-denied stop, the signature never occurs.
Expected
If the motionchange location can't be fetched at stopTimeout, the SDK should still end up in a recoverable stationary state — e.g. retry the fetch when location becomes available, arm the region from the last known location, or let an SLC/motion wake re-run the stop positioning — rather than entering Stationary with a nil region that permanently vetoes promotion.
Actual
Stationary with no region: onUpdateState evaluates Is location beyond stationaryRadius? Δd: 0.0, radius: 0.0 and stays stationary on every wake. Nothing recovers it until the app is manually foregrounded. With stopOnTerminate: false and an always-granted user who rarely opens the app, this is many hours of silent tracking loss.
Repro sketch
stopOnTerminate: false,startOnBoot: true, default stationaryRadius, tracking active in a moving state.- Move into a GPS-denied environment (underground station / deep indoors) and stay still so
stopTimeoutfires while CoreLocation returnskCLErrorDomain Code=0. - Observe
Failed to fetch motionchange locationfollowed by Stationary entry with no region; subsequent movement never promotes (log:radius: 0.0on the stationaryRadius checks).
Checked against 5.4.0's comparator fix (not the same bug)
5.4.0's changelog fixes an iOS case where a future-stamped location poisons the last-location comparator and all subsequent locations are rejected until restart. We looked for that signature in this log before filing, since it is in the same family (silent loss, healed only by a restart).
It is not the cause here. The log carries 9 [Metrics] Clock skew detected: current.timestamp < prev.timestamp warnings, but they cluster at SDK init (7 of 9 within ~5 minutes of a TSLocationManager init, i.e. a fresh process comparing against a persisted previous fix), and they do not bracket the wedge: the last one before the wedge is ~30 minutes earlier, three more fire after the manual open healed it, and locations continue to flow normally around each. The wedge's own trigger is unambiguous in the timeline — the failed motionchange fetch at stopTimeout, after which the stationary-radius check reads radius: 0.0 and never recovers.
We are now running 5.4.0 (TSLocationManager 4.4.1) and will report if the zero-radius state reappears on it.
Workaround we ship
On boot/foreground while stationary we re-assert the stop sequence (getCurrentPosition() then changePace(false)), which re-runs the motionchange positioning and arms the region. That heals the wedge whenever our code gets to run — but the underlying nil-region veto looks like an SDK-side state bug worth fixing at the source.
Full log available on request (the excerpt in the log field is verbatim from logger.getLog / the emailed log db).
Plugin Code and/or Config
// Excerpt — the config relevant to this report (licensed, iOS)
BackgroundGeolocation.ready({
stopOnTerminate: false,
startOnBoot: true,
stopTimeout: 5,
desiredAccuracy: BackgroundGeolocation.DesiredAccuracy.High,
locationAuthorizationRequest: 'Always', // granted
// stationaryRadius: default (25)
});
Relevant log output
17:20:30 UTC 🔵 -[TSTrackingService onStopTimeout] 🛑 stopTimeout fired
17:20:30 UTC 🟢 -[TSLocationRequestService requestLocation:] [motionchange] maximumAge: 5000
17:20:35 UTC ⚠️ -[TSTrackingService changePace:]_block_invoke TODO Failed to fetch motionchange location: Error Domain=kCLErrorDomain Code=0 "(null)"
17:20:35 UTC ⚠️ -[TSTrackingService onMotionChangeError:] Failed to receive motionchange location with error: Error Domain=kCLErrorDomain Code=0 "(null)"
17:20:35 UTC 🟢 -[TSTrackingService startMonitoringSignificantLocationChanges]
17:24:00 UTC 🔵 -[TSTrackingService onUpdateState:] Is location beyond stationaryRadius? 📍Δd: 0.0, radius: 0.0
...
(the same "Δd: 0.0, radius: 0.0" veto repeats 40 times across ~18.4 h — through a full morning of walking + rail + driving)
11:45:56 UTC (next day) — last zero-radius veto; a manual app open at 11:46 UTC re-armed the region and tracking resumed mid-journey
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing TSTrackingService's onStopTimeout, changePace, onMotionChangeError, startMonitoringSignificantLocationChanges, and onUpdateState paths after the motionchange fetch fails. Reproduce the GPS-denied stop and verify that a later SLC or motion wake can recover tracking without repeated stationary-radius checks reporting radius 0.0.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ios, react-native, typescript
- Domain
- mobile-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100