diff --git a/apps/macos/Sources/OpenClaw/PermissionManager.swift b/apps/macos/Sources/OpenClaw/PermissionManager.swift index 8d2126619c39..42cbfa0b71a5 100644 --- a/apps/macos/Sources/OpenClaw/PermissionManager.swift +++ b/apps/macos/Sources/OpenClaw/PermissionManager.swift @@ -174,7 +174,7 @@ enum PermissionManager { } return false } - let status = CLLocationManager().authorizationStatus + let status = await self.locationAuthorizationStatus() switch status { case .authorizedAlways, .authorizedWhenInUse, .authorized: return true @@ -198,6 +198,10 @@ enum PermissionManager { return mic && speech } + static func locationAuthorizationStatus() async -> CLAuthorizationStatus { + await LocationPermissionRequester.shared.authorizationStatus + } + static func ensureVoiceWakePermissions(interactive: Bool) async -> Bool { let results = await self.ensure([.microphone, .speechRecognition], interactive: interactive) return results[.microphone] == true && results[.speechRecognition] == true @@ -241,7 +245,7 @@ enum PermissionManager { ? .granted : .notGranted case .location: - let status = CLLocationManager().authorizationStatus + let status = await self.locationAuthorizationStatus() results[cap] = CLLocationManager.locationServicesEnabled() && self.isLocationAuthorized(status: status, requireAlways: false) ? .granted : .notGranted } @@ -303,6 +307,11 @@ final class LocationPermissionRequester: NSObject, CLLocationManagerDelegate { self.manager.delegate = self } + var authorizationStatus: CLAuthorizationStatus { + // Core Location retains per-manager framework state, so status polling must reuse this process-lifetime owner. + self.manager.authorizationStatus + } + func request(always: Bool) async -> CLAuthorizationStatus { let current = self.manager.authorizationStatus if PermissionManager.isLocationAuthorized(status: current, requireAlways: always) { diff --git a/apps/macos/Sources/OpenClaw/PermissionsSettings.swift b/apps/macos/Sources/OpenClaw/PermissionsSettings.swift index d281db72fc57..e9b3e96ff720 100644 --- a/apps/macos/Sources/OpenClaw/PermissionsSettings.swift +++ b/apps/macos/Sources/OpenClaw/PermissionsSettings.swift @@ -155,7 +155,7 @@ private struct LocationAccessSettings: View { return false } - let status = CLLocationManager().authorizationStatus + let status = await PermissionManager.locationAuthorizationStatus() let requireAlways = mode == .always if PermissionManager.isLocationAuthorized(status: status, requireAlways: requireAlways) { return true diff --git a/apps/macos/Tests/OpenClawIPCTests/PermissionManagerTests.swift b/apps/macos/Tests/OpenClawIPCTests/PermissionManagerTests.swift index 74dff4e28a2d..1715e2bb5833 100644 --- a/apps/macos/Tests/OpenClawIPCTests/PermissionManagerTests.swift +++ b/apps/macos/Tests/OpenClawIPCTests/PermissionManagerTests.swift @@ -24,15 +24,19 @@ struct PermissionManagerTests { #expect(ensured.keys.count == caps.count) } - @Test func `location status matches authorization always`() async { - let status = CLLocationManager().authorizationStatus + @Test func `location status matches canonical authorization`() async { + let status = await PermissionManager.locationAuthorizationStatus() let results = await PermissionManager.grantedStatus([.location]) - #expect(results[.location] == (status == .authorizedAlways)) + let expected = CLLocationManager.locationServicesEnabled() + && PermissionManager.isLocationAuthorized(status: status, requireAlways: false) + #expect(results[.location] == expected) } - @Test func `ensure location non interactive matches authorization always`() async { - let status = CLLocationManager().authorizationStatus + @Test func `ensure location non interactive matches canonical authorization`() async { + let status = await PermissionManager.locationAuthorizationStatus() let ensured = await PermissionManager.ensure([.location], interactive: false) - #expect(ensured[.location] == (status == .authorizedAlways)) + let expected = CLLocationManager.locationServicesEnabled() + && PermissionManager.isLocationAuthorized(status: status, requireAlways: false) + #expect(ensured[.location] == expected) } }