immich-app/immich · error · BadRequestException
Cannot unlink Android motion photos
Error message
Cannot unlink Android motion photos
What it means
Thrown by onBeforeUnlink when attempting to unlink a live photo motion video that lives at an Android motion photo path. Android motion photos embed the video inside the photo's storage layout, so unlinking would corrupt the paired file; Immich blocks it.
Solutions
- Skip unlinking for assets whose originalPath matches an Android motion path
- Use the Android motion photo removal workflow (reprocess the asset without motion extraction) instead of unlink
- Only unlink motion videos that were uploaded as separate iOS-style live photo videos
Example fix
// before
await onBeforeUnlink(repos, { livePhotoVideoId: motion.id });
// after
if (StorageCore.isAndroidMotionPath(motion.originalPath)) {
console.warn(`Skip unlink: ${motion.id} is an Android motion photo`);
} else {
await onBeforeUnlink(repos, { livePhotoVideoId: motion.id });
} Defensive patterns
Strategy: validation
Validate before calling
if (motion.originalPath && /MotionPhoto|motionphoto|EmbeddedVideo/i.test(motion.originalPath)) {
// treat as Android motion photo; skip unlink
} Try / catch
try {
await unlinkLivePhoto(livePhotoVideoId);
} catch (e) {
if (e.status === 400 && /Android motion/.test(e.message)) {
// use the motion-photo removal/reprocess workflow instead
}
} Prevention
- Filter out Android motion photo assets in bulk unlink jobs
- Detect Android motion pairs by path pattern before mutating
- Prefer Immich's dedicated motion photo workflows over manual unlink
When it happens
Trigger: Calling onBeforeUnlink on a motion video whose originalPath matches StorageCore.isAndroidMotionPath, i.e. the video was extracted from / belongs to an Android motion photo.
Common situations: Bulk unlink scripts processing all live photo videos indiscriminately; users trying to detach motion from an Android-captured photo; migration or dedup jobs touching Android motion pairs.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- Editing live photos is not supported
- Live photo video must be a video
- Live photo video not found
- Asset not found
- Asset not found
AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15).
Data as JSON: /api/errors/a283d219c66ca03f.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/utils/asset.util.ts:176
}
if (motionAsset && motionAsset.visibility === AssetVisibility.Timeline) {
await assetRepository.update({ id: livePhotoVideoId, visibility: AssetVisibility.Hidden });
await eventRepository.emit('AssetHide', { assetId: motionAsset.id, userId });
}
};
export const onBeforeUnlink = async (
{ asset: assetRepository }: AssetHookRepositories,
{ livePhotoVideoId }: { livePhotoVideoId: string },
) => {
const motion = await assetRepository.getById(livePhotoVideoId);
if (!motion) {
return null;
}
if (StorageCore.isAndroidMotionPath(motion.originalPath)) {
throw new BadRequestException('Cannot unlink Android motion photos');
}
return motion;
};
export const onAfterUnlink = async (
{ asset: assetRepository, event: eventRepository }: AssetHookRepositories,
{ userId, livePhotoVideoId, visibility }: { userId: string; livePhotoVideoId: string; visibility: AssetVisibility },
) => {
await assetRepository.update({ id: livePhotoVideoId, visibility });
await eventRepository.emit('AssetShow', { assetId: livePhotoVideoId, userId });
};
export function mapToUploadFile(file: ImmichFile): UploadFile {
return {
uuid: file.uuid,
checksum: file.checksum,
originalPath: file.path,View on GitHub (pinned to e55ac299a4)