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
- 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.
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
- 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
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
- Cannot add another owner
- Cannot derive a mobile build number from
- Cannot remove the last album owner
- Cast session is not established
- Failed to create
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)