immich-app/immich · error · PigeonError

NO_AUTH

NO_AUTH

Error message

No photo library access

What it means

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.

Solutions

  1. Open iOS Settings > Privacy & Security > Photos > (app) and select 'Full Access' (or 'All Photos').
  2. Reinstall or re-prompt: if permanently denied, the app must direct the user to Settings (permission cannot be re-requested in-app).
  3. Ensure the app's Info.plist includes NSPhotoLibraryUsageDescription (and add usage description) so the prompt can be shown.
  4. If only limited access is desired, handle the limited status explicitly instead of requiring .authorized, or use PHPhotoLibrary.requestAuthorization for readWrite.
  5. On the Flutter side, catch the NO_AUTH code and surface a 'grant photo access' screen.

Example fix

// before (Info.plist missing entry -> prompt never appears)
// after (Info.plist)
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs photo library access to back up your photos.</string>
Defensive patterns

Strategy: try-catch

Validate before calling

let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
if #available(iOS 14, *) { status == .limited ? requestFullAccess() : () }
guard status == .authorized || status == .limited else { promptOpenSettings(); return }

Try / catch

do { let delta = try await sync.getMediaChanges() } catch let e as PigeonError where e.code == "NO_AUTH" { await MainActor.run { openAppPhotoPermissionSettings() } }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/a81dead727f740a0. Report an issue: GitHub.

Appendix: source

Thrown at mobile/ios/Runner/Sync/MessagesImpl.swift:151

        }
        
        albums.append(domainAlbum)
      }
    }
    return albums.sorted { $0.id < $1.id }
  }
  
  func getMediaChanges(completion: @escaping (Result<SyncDelta, Error>) -> Void) {
    runSync(completion) { try $0.getMediaChanges() }
  }
  
  private func getMediaChanges() throws -> SyncDelta {
    guard #available(iOS 16, *) else {
      throw PigeonError(code: kUnsupportedOSError, message: "This feature requires iOS 16 or later.", details: nil)
    }
    
    guard PHPhotoLibrary.authorizationStatus(for: .readWrite) == .authorized else {
      throw PigeonError(code: "NO_AUTH", message: "No photo library access", details: nil)
    }
    
    guard let storedToken = getChangeToken() else {
      // No token exists, definitely need a full sync
      print("MediaManager::getMediaChanges: No token found")
      throw PigeonError(code: "NO_TOKEN", message: "No stored change token", details: nil)
    }
    
    let currentToken = PHPhotoLibrary.shared().currentChangeToken
    if storedToken == currentToken {
      return SyncDelta(hasChanges: false, updates: [], deletes: [], assetAlbums: [:])
    }
    
    let changes = try PHPhotoLibrary.shared().fetchPersistentChanges(since: storedToken)
    
    var updatedAssets: Set<AssetWrapper> = []
    var deletedAssets: Set<String> = []
    

View on GitHub (pinned to e55ac299a4)