{"record":{"id":"12e0b47af9ec6f89","repo":"immich-app/immich","slug":"kunsupportedoserror","errorCode":"kUnsupportedOSError","errorMessage":"This feature requires iOS 16 or later.","messagePattern":"This feature requires iOS 16 or later\\.","errorType":"error_code","errorClass":"PigeonError","httpStatus":null,"severity":"error","filePath":"mobile/ios/Runner/Sync/MessagesImpl.swift","lineNumber":147,"sourceCode":"        )\n        \n        if let firstAsset = assets.firstObject {\n          domainAlbum.updatedAt = firstAsset.modificationDate.map { Int64($0.timeIntervalSince1970) }\n        }\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)","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/mobile/ios/Runner/Sync/MessagesImpl.swift#L129-L165","documentation":"getMediaChanges on iOS relies on PHPhotoLibrary change-observation / sync APIs gated behind iOS 16. When running on iOS 15 or earlier, the implementation throws a PigeonError with code kUnsupportedOSError stating the feature requires iOS 16+.","triggerScenarios":"Calling getMediaChanges on a device or simulator running iOS < 16; an OS-version check was not performed before invoking the sync delta API.","commonSituations":"Users on older iPhones/iPads; CI or manual testing on an iOS 15 simulator; app supporting a minimum deployment target below iOS 16 while using this API unconditionally.","solutions":["Require iOS 16+ for this feature and gate the call with #available(iOS 16, *) before invoking","Fall back to a full library re-sync (or the legacy sync path) on iOS 15","Raise the deployment target to iOS 16 if the delta-sync feature is essential"],"exampleFix":"// before\nlet delta = try await api.getMediaChanges()\n// after\nif #available(iOS 16, *) {\n  let delta = try await api.getMediaChanges()\n} else {\n  let delta = try await fullResync() // legacy fallback\n}","handlingStrategy":"type-guard","validationCode":"let syncAvailable: Bool {\n  if #available(iOS 16, *) { return true }\n  return false\n}","typeGuard":"func supportsMediaChanges() -> Bool {\n  if #available(iOS 16, *) { return true }\n  return false\n}","tryCatchPattern":"do {\n  let delta = try await impl.getMediaChanges()\n} catch let e as PigeonError where e.code == kUnsupportedOSError {\n  fallBackToFullResync()\n}","preventionTips":["Gate on #available(iOS 16, *) before delta-sync calls","Set an explicit minimum-OS policy per feature","Test on the oldest supported iOS runtime"],"tags":["ios","version-gate","sync","pigeon"],"backgroundTag":"unsupported-platform","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"}