{"record":{"id":"a81dead727f740a0","repo":"immich-app/immich","slug":"no-auth","errorCode":"NO_AUTH","errorMessage":"No photo library access","messagePattern":"No photo library access","errorType":"error_code","errorClass":"PigeonError","httpStatus":null,"severity":"error","filePath":"mobile/ios/Runner/Sync/MessagesImpl.swift","lineNumber":151,"sourceCode":"        }\n        \n        albums.append(domainAlbum)\n      }\n    }\n    return albums.sorted { $0.id < $1.id }\n  }\n  \n  func getMediaChanges(completion: @escaping (Result<SyncDelta, Error>) -> Void) {\n    runSync(completion) { try $0.getMediaChanges() }\n  }\n  \n  private func getMediaChanges() throws -> SyncDelta {\n    guard #available(iOS 16, *) else {\n      throw PigeonError(code: kUnsupportedOSError, message: \"This feature requires iOS 16 or later.\", details: nil)\n    }\n    \n    guard PHPhotoLibrary.authorizationStatus(for: .readWrite) == .authorized else {\n      throw PigeonError(code: \"NO_AUTH\", message: \"No photo library access\", details: nil)\n    }\n    \n    guard let storedToken = getChangeToken() else {\n      // No token exists, definitely need a full sync\n      print(\"MediaManager::getMediaChanges: No token found\")\n      throw PigeonError(code: \"NO_TOKEN\", message: \"No stored change token\", details: nil)\n    }\n    \n    let currentToken = PHPhotoLibrary.shared().currentChangeToken\n    if storedToken == currentToken {\n      return SyncDelta(hasChanges: false, updates: [], deletes: [], assetAlbums: [:])\n    }\n    \n    let changes = try PHPhotoLibrary.shared().fetchPersistentChanges(since: storedToken)\n    \n    var updatedAssets: Set<AssetWrapper> = []\n    var deletedAssets: Set<String> = []\n    ","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/mobile/ios/Runner/Sync/MessagesImpl.swift#L133-L169","documentation":"In the iOS native sync bridge (Sync/MessagesImpl.swift), getMediaChanges() requires full readWrite photo-library authorization before computing a sync delta. If PHPhotoLibrary.authorizationStatus(for: .readWrite) is not .authorized, it throws a PigeonError with code NO_AUTH, because reading/creating assets in the user's photo library is impossible without permission.","triggerScenarios":"Invoking the media-changes/sync method from Flutter when the app has been denied photo access, only granted limited access (iOS 14+ 'Select Photos'), or the permission prompt was never answered.","commonSituations":"Fresh install where the user tapped 'Don't Allow'; user chose 'Select Photos...' (limited) instead of 'Allow Full Access'; permission revoked in Settings after install; MDM/profiles restricting photo access.","solutions":["Open iOS Settings > Privacy & Security > Photos > (app) and select 'Full Access' (or 'All Photos').","Reinstall or re-prompt: if permanently denied, the app must direct the user to Settings (permission cannot be re-requested in-app).","Ensure the app's Info.plist includes NSPhotoLibraryUsageDescription (and add usage description) so the prompt can be shown.","If only limited access is desired, handle the limited status explicitly instead of requiring .authorized, or use PHPhotoLibrary.requestAuthorization for readWrite.","On the Flutter side, catch the NO_AUTH code and surface a 'grant photo access' screen."],"exampleFix":"// before (Info.plist missing entry -> prompt never appears)\n// after (Info.plist)\n<key>NSPhotoLibraryUsageDescription</key>\n<string>This app needs photo library access to back up your photos.</string>","handlingStrategy":"try-catch","validationCode":"let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)\nif #available(iOS 14, *) { status == .limited ? requestFullAccess() : () }\nguard status == .authorized || status == .limited else { promptOpenSettings(); return }","typeGuard":null,"tryCatchPattern":"do { let delta = try await sync.getMediaChanges() } catch let e as PigeonError where e.code == \"NO_AUTH\" { await MainActor.run { openAppPhotoPermissionSettings() } }","preventionTips":["Declare NSPhotoLibraryUsageDescription (and add usage descriptions) in Info.plist","Request authorization up-front with PHPhotoLibrary.requestAuthorization(for: .readWrite)","Detect .limited status and guide users to upgrade to full access","Provide a settings deep-link when permission is permanently denied"],"tags":["ios","permissions","photo-library","mobile"],"backgroundTag":"permission-denied","analyzedSha":"e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}