Background location doesn't work - Significant-location-change background trigger never fires
Nobody has claimed this yet.
- Dominant language
- Swift
- Stars
- 2.5k
- Forks
- 1k
- Avg merge
- 2d 18h
- Merged PRs (30d)
- 13
Description
Bug report
How to use GitHub
- Please use the π reaction to show that you are affected by the same issue.
- Please don't comment if you have no relevant information to add. It's just extra noise for everyone subscribed to this issue.
- Subscribe to receive notifications on status change and new comments.
Steps to reproduce
- Enable background location and allow app to receive them in settings
- Activate auto-upload
- Foreground app once, don't force quit it
- use your phone normally and move around for a few days without rebooting your phone
- foreground app
Expected behaviour
At least one photo should have been uploaded, should be visible on the server
Actual behaviour
Nothing happens
Logs
See below
Reasoning or why should it be changed/implemented?
Make background update work. Bonus: with this and (#4304) solved, there is probably no reason to keep the "keep app active" banner, as no more unintended transfer interruption can occur.
Environment data
Device: iPhone 12 mini, iOS 26.6.1, "Always" location authorization, location background mode present,
auto-upload on, Wi-Fi off during the outings unless stated.
Code references are against upstream master at 204f9e23b1 (2026-08-23). The log lines quoted come
from a build with diagnostic logging added locally (Location update delivered, Re-arming location monitoring at launch, getCameraRollAssets: counts); upstream only logs Triggered by location change.
Analysis
This really is one single issue, but wrote up as two parts simply because it took two rounds to discover the whole of it, and I got lazy over merging them...
Note 1: also, the use of .fitness is questionable, as I believe .other makes more sense for everyday use:
Use this activity type to describe positioning in activities that arenβt covered by one of the other activity types. This includes activities without a specific user intention, for example, positioning while a user sits on a bench interacting with a device.
Note 2: when this is solved, we're one fix away (#4304) from removing the need for the _keep_running_ notification banner (since Location trigger can actually relaunch an force-quit app).
Part 1 β CLLocationManager is created on a thread with no run loop, so its delegate is never called
Apple's contract
Core Location calls the methods of your delegate object using the RunLoop of the thread on which you
initialized theCLLocationManagerobject. That thread must itself have an active RunLoop, like the one
found in your app's main thread.
Core Location calls the methods of your delegate object on the runloop from the thread on which you
initializedCLLocationManager. That thread must itself have an active run loop, like the one found in
your app's main thread.
startMonitoringSignificantLocationChanges():
The first event to be delivered is usually the most recently cached location event (if any) but may be a
newer event in some circumstances. [β¦] When you restart location services, the current event is
delivered to your delegate immediately.
So every call to startβ¦ on a correctly-created manager should produce at least one delegate call β the
initial cached location β immediately.
Where the code departs from it
iOSClient/NCBackgroundLocationUploadManager.swift (master, lines 9β24): the manager is a lazily-created
singleton whose initialiser creates the CLLocationManager and sets delegate = self:
static let shared = NCBackgroundLocationUploadManager()
β¦
private let locationManager = CLLocationManager()
β¦
private override init() {
super.init()
locationManager.delegate = self
locationManager.activityType = .fitness
locationManager.allowsBackgroundLocationUpdates = true
}
iOSClient/SceneDelegate.swift (master, lines 273β289): the only place in normal use that touches
.shared β and therefore the thread the manager is created on β is a group.addTask { β¦ } closure
inside sceneDidEnterBackground:
let didFinish = await withTaskGroup(of: Bool.self) { group -> Bool in
group.addTask {
β¦
// LOCATION MANAGER
if CLLocationManager().authorizationStatus == .authorizedAlways && NCPreferences().location && tblAccount.autoUploadStart {
NCBackgroundLocationUploadManager.shared.start()
} else {
NCBackgroundLocationUploadManager.shared.stop()
}
A task-group child runs on a Swift cooperative-pool thread, which has no run loop. The only other caller
is the settings screen (NCAutoUploadModel.swift:250), which is not reached in ordinary use.
Field evidence
Before: across every log from 2026-09-10 to 2026-09-15 β five days, walks, a 300 km drive, a 5 km
drive, force-quit and left-alive variants β monitoring was armed over a hundred times and
Triggered by location change appeared zero times. Not even the initial cached-location delivery
Apple describes.
| Log | Span | Location monitoring started | Triggered by location change |
|---|---|---|---|
| log.txt | 09-10 18:42 β 09-11 14:47 | 17 | 0 |
| log-2 | 09-11 15:07 β 16:19 | 4 | 0 |
| log-3 (300 km drive) | 09-11 15:07 β 23:39 | 7 | 0 |
| log-4 | 09-11 23:40 β 09-12 11:03 | 9 | 0 |
| log-5b | 09-13 06:22 β 22:57 | 19 | 0 |
| log-6 | 09-13 23:32 β 23:59 | 18 | 0 |
| log-8 | 09-14 00:06 β 00:32 | 14 | 0 |
| log-9 | 09-14 00:34 β 08:27 | 8 | 0 |
| log-b | 09-14 08:50 β 11:11 | 2 | 0 |
| log-c | 09-14 11:40 β 09-15 07:42 | 4 | 0 |
| log-d (1 km walk) | 09-15 08:07 β 10:17 | 1 | 0 |
| log-e (1 km walk) | 09-15 08:07 β 12:47 | 5 | 0 |
iOS itself reported location use: the system's "Nextcloud has used your location in the background"
notice was shown on the device during this period, i.e. the OS considered the registration active. The
app simply had nowhere to deliver to.
After creating the manager on the main thread (everything else unchanged), the very first arming
delivered β log-f, 2026-09-15:
12:57:04 [DEBUG] Location monitoring started
12:57:04 [LOCATION] Location update delivered: <redacted> β same second: the initial delivery
12:57:04 [LOCATION] Triggered by location change: <redacted>
β¦
14:45:42 [DEBUG] Location monitoring started
14:45:42 [LOCATION] Location update delivered: <redacted> β second arming, second immediate delivery
and every arming since (log-g/h/i/j: 15 deliveries over the following day and a half).
Test scripts for log-d / log-e (the two "before" 1km+ walks on the day of the diagnosis)
log-d:
- force kill shortly after 8:00, WiFi still on
- deactivate WiFi at 8:35
- start moving at 8:40, arrive at 8:55 1 km away
- resume moving at 9:45, arrive at 10 back at the starting point
- at 10:15, reactivate WiFi, wait a bit, foreground app, wait for popup, extract log-d.txt
...taking photos, videos, and briefly activating the phone from time to time throughout the experiment.
log-e:
- leave 11:03
- arrival 11:15
- start coming back at 11:48
- arrival 12:08
- check server: nothing uploaded yet since approx 2 hours ago
- connect to wifi at 12:23
- foreground app at 12:46
Part 2 β a process relaunched by iOS never re-arms monitoring, so relaunches for location events do nothing
Apple's contract
startMonitoringSignificantLocationChanges():
If you start this service and your app is subsequently terminated, the system automatically relaunches
the app into the background if a new event arrives. In such a case, the options dictionary passed to the
application(_:willFinishLaunchingWithOptions:)andapplication(_:didFinishLaunchingWithOptions:)
methods of your app delegate contains the keylocationto indicate that your app was launched because
of a location event. Upon relaunch, you must still configure a location manager object and call this
method to continue receiving location events. When you restart location services, the current event is
delivered to your delegate immediately.
UIApplication.LaunchOptionsKey.location:
You should use the presence of this key as a signal to create a
CLLocationManagerobject and start
location services again. Location data is delivered only to the location manager delegate and not using
this key.
Where the code departs from it
iOSClient/AppDelegate.swift (master) contains no reference to NCBackgroundLocationUploadManager,
CLLocationManager, or LaunchOptionsKey.location. The only arming site is sceneDidEnterBackground
(above), which does not run for a process that begins life in the background. A process iOS relaunches
for a location event β or for any other reason (BGTask, background URLSession) β therefore has no manager
and no delegate for the lifetime of that process.
Field evidence
Bare relaunches with nothing after them. In every outing before the re-arm, the log shows iOS
relaunching the app mid-trip ([START] Start session with no preceding foreground and no user action),
followed by nothing location-related at all. log-e, walk of 11:03β12:08:
11:16:00 [START] Start session with level verbose Nextcloud β¦ β relaunched mid-walk; nothing follows
11:58:15 [START] Start session with level verbose Nextcloud β¦ β relaunched on the way back; nothing follows
12:46:57 [START] Start session with level verbose Nextcloud β¦
12:46:59 [DEBUG] Application will enter in foreground β tester opens the app
12:47:00 [DEBUG] Automatic upload, new 18 assets found β the whole walk's photos, found at once
log-d, walk of 08:40β10:00: relaunches at 09:52:24 and 10:16:41, nothing after either. log-f (5 km
drive, Part 1 already fixed): relaunches at 13:55:26 and 14:24:56, nothing after either; the drive's 16
photos were found by a BGAppRefreshTask at 14:41:30, not by location.
launchOptions[.location] was never observed. With a re-arm keyed on that option, the log line it
emits (Launched for a location event) appears zero times across every subsequent log (log-f through
log-j, 2026-09-15 β 09-16), including the relaunches below that are unambiguously location-triggered.
Under this app's scene-based lifecycle the key documented above did not arrive in
application(_:didFinishLaunchingWithOptions:) on this device.
After re-arming on every launch β log-g, 2026-09-15, app force-quit before the walk:
15:18:31 [DEBUG] App is terminating β tester force-kills; walk starts 15:22
15:35:11 [START] Start session with level verbose Nextcloud β¦ β relaunched ~13 min / ~500 m into the walk
15:35:11 [LOCATION] Re-arming location monitoring at launch
15:35:11 [DEBUG] Location monitoring started
15:35:11 [LOCATION] Location update delivered: <redacted>
15:35:12 [LOCATION] Triggered by location change: <redacted>
16:01:06 [START] Start session with level verbose Nextcloud β¦ β relaunched on arrival home
16:01:06 [LOCATION] Re-arming location monitoring at launch
16:01:06 [DEBUG] Location monitoring started
16:01:06 [LOCATION] Location update delivered: <redacted>
16:01:08 [LOCATION] Triggered by location change: <redacted>
No BGAppRefreshTask ran between 15:09:00 and 16:09:50 and no upload was in flight, so neither relaunch
is attributable to a BGTask or a background-URLSession wake; both coincide with ~500 m of movement and with
arrival, matching the documented SLC behaviour, after a force-quit. These are the first
movement-triggered relaunches on record for this app.
Subsequent outings (log-h 17:13:54 relaunch three minutes after leaving home; log-i 20:54:33 relaunch;
log-j four deliveries into a never-terminated process over 3Β½ h) show the same chain each time.
Test script for log-g
- 15:18 : force kill the backgrounded app, take a few pictures, take wifi off, lock screen
- 15:22 start walking -- approx 700m away in straight line and back, but I walked over 1,5km total
- 16:01-16:03 : elevator, home
- 16:04 : charge. At this stage there is no visible (complete) upload on the server
- 16:10-16:11 : pictures uploaded
- 16:30 : foreground and extract log
Appendix β the whole record in one table
"Armed" = Location monitoring started; "Launched for location event" = a diagnostic line emitted only
when launchOptions[.location] != nil; "Re-armed" = the every-launch re-arm's line; "Delivered" = a
diagnostic line emitted at the top of didUpdateLocations, before any guard (present from 09-15 ~08:00);
"Triggered" = upstream's own Triggered by location change; "Bare starts" = [START] Start session
lines, i.e. process launches.
| Log | Span | Armed | Launched for location event | Re-armed | Delivered | Triggered | Bare starts |
|---|---|---|---|---|---|---|---|
| log.txt | 09-10 18:42 β 09-11 14:47 | 17 | β | β | β | 0 | 7 |
| log-2 | 09-11 15:07 β 16:19 | 4 | β | β | β | 0 | 2 |
| log-3 | 09-11 15:07 β 23:39 | 7 | β | β | β | 0 | 4 |
| log-4 | 09-11 23:40 β 09-12 11:03 | 9 | β | β | β | 0 | 5 |
| log-5b | 09-13 06:22 β 22:57 | 19 | β | β | β | 0 | 3 |
| log-6 | 09-13 23:32 β 23:59 | 18 | β | β | β | 0 | 1 |
| log-8 | 09-14 00:06 β 00:32 | 14 | β | β | β | 0 | 1 |
| log-9 | 09-14 00:34 β 08:27 | 8 | β | β | β | 0 | 3 |
| log-b | 09-14 08:50 β 11:11 | 2 | β | β | β | 0 | 1 |
| log-c | 09-14 11:40 β 09-15 07:42 | 4 | β | β | β | 0 | 5 |
| log-d | 09-15 08:07 β 10:17 | 1 | β | β | 0 | 0 | 3 |
| log-e | 09-15 08:07 β 12:47 | 5 | β | β | 0 | 0 | 9 |
| log-f (Part 1 fixed) | 09-15 12:47 β 14:45 | 3 | 0 | β | 2 | 2 | 3 |
| log-g (both fixed) | 09-15 14:52 β 16:31 | 6 | 0 | 4 | 4 | 4 | 4 |
| log-h | 09-15 16:47 β 17:52 | 5 | 0 | 2 | 3 | 2 | 2 |
| log-i | 09-15 17:52 β 09-16 08:50 | 8 | 0 | 3 | 5 | 3 | 3 |
| log-j | 09-16 09:03 β 12:27 | 3 | 0 | 0 | 4 | 4 | 0 |
(log-h/i/j "Delivered" exceeds "Triggered" where a delivery arrived while the app was in the foreground and
was intentionally ignored.)
Contributor guide
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
Read iOSClient/NCBackgroundLocationUploadManager.swift and iOSClient/SceneDelegate.swift to trace where CLLocationManager is created and monitoring is armed. Then inspect iOSClient/AppDelegate.swift for launch handling and NCAutoUploadModel.swift for the other arming path. Done means location updates reach the delegate during background use and a relaunch re-arms monitoring; verify with the issue's reproduction steps and logs.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ios, swift
- Domain
- mobile
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 58/100