immich-app/immich · info · PigeonError
NO_TOKEN
NO_TOKEN
Error message
No stored change token
What it means
getMediaChanges in MessagesImpl.swift throws PigeonError(code: "NO_TOKEN") when no previously persisted PhotoKit change token exists. PhotoKit change tokens let the app fetch only incremental media changes since the last sync; without a stored token the delta cannot be computed, so a full sync is required instead. The error is a signal to the Dart side to fall back to a full media sync and then persist the new token.
Solutions
- Catch the NO_TOKEN code on the Dart side and perform a full media sync (getMediaItems) instead of an incremental one.
- After the full sync succeeds, persist the current PHPhotoLibrary change token via the platform channel so future calls return deltas.
- Verify the code that stores the token after a successful sync actually runs (e.g. it is not skipped on background termination).
Example fix
// before
try await syncApi.getMediaChanges();
// after
try {
delta = await syncApi.getMediaChanges();
} on PlatformException catch (e) {
if (e.code == 'NO_TOKEN') {
delta = await fullSyncAndStoreToken(); // full fetch, then save token
} else {
rethrow;
}
} Defensive patterns
Strategy: fallback
Validate before calling
// Dart: check whether a token has been stored before requesting deltas
final hasToken = await prefs.getBool('hasChangeToken') ?? false; Try / catch
try {
delta = await api.getMediaChanges();
} on PlatformException catch (e) {
if (e.code == 'NO_TOKEN') delta = await fullSync(); else rethrow;
} Prevention
- Always run a full sync first on fresh installs before incremental syncs.
- Persist the change token immediately after every successful sync.
- Handle app-data-clear/reinstall by resetting to full-sync mode.
When it happens
Trigger: Calling getMediaChanges before any full sync has ever stored a change token (e.g. fresh install, app data cleared, or getMediaChanges called before the initial full media listing).
Common situations: First launch of the app; after reinstall or 'clear data'; after a bug or migration wiped the stored token from UserDefaults/keychain; calling the incremental sync endpoint from a fresh client that never completed an initial sync.
Understand the failure class
Background: "missing required config value" errors: why libraries refuse to start when a configuration key is empty, unset, or blank — this error's family across 48 libraries.
Related errors
- kUnsupportedOSError
- Invalid ack type
- Method not supported on this Android version.
- NO_AUTH
- No valid import paths found for library
AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15).
Data as JSON: /api/errors/0ca24d4c30e42d3d.
Report an issue: GitHub.
Appendix: source
Thrown at mobile/ios/Runner/Sync/MessagesImpl.swift:157
}
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> = []
for change in changes {
try Task.checkCancellation()
guard let details = try? change.changeDetails(for: PHObjectType.asset) else { continue }
let updated = details.updatedLocalIdentifiers.union(details.insertedLocalIdentifiers)
deletedAssets.formUnion(details.deletedLocalIdentifiers)View on GitHub (pinned to e55ac299a4)