immich-app/immich · error · BadRequestException
Live photo video not found
Error message
Live photo video not found
What it means
Thrown by onBeforeLink when linking a live photo's motion (video) component to an asset, but no asset exists for the provided livePhotoVideoId. Immich requires the motion video to be an existing asset before it can be attached to the image asset. This prevents dangling references to non-existent assets.
Solutions
- Verify the livePhotoVideoId references an existing asset via GET /api/assets/:id
- Re-upload the missing motion video component and retry the link
- Check the client is pointing at the correct server (asset IDs are not portable across instances)
- Clear stale client-side cache of live photo pairs and re-pair assets
Example fix
// before
await onBeforeLink(repos, { userId, livePhotoVideoId: staleId });
// after
const motion = await assetRepository.getById(staleId);
if (!motion) {
console.warn(`Motion asset ${staleId} missing; re-uploading before link`);
const fresh = await uploadMotionVideo(pair);
await onBeforeLink(repos, { userId, livePhotoVideoId: fresh.id });
} Defensive patterns
Strategy: validation
Validate before calling
const motion = await api.getAsset(livePhotoVideoId);
if (!motion) throw new Error(`Motion asset ${livePhotoVideoId} not found; re-upload before linking`); Try / catch
try {
await linkLivePhoto(assetId, livePhotoVideoId);
} catch (e) {
if (e.status === 400 && /not found/.test(e.message)) {
// re-upload motion video and retry link
}
} Prevention
- Resolve live photo pair IDs right before linking, not from long-lived cache
- Handle failed video uploads before attempting the link
- Use server-provided pairing metadata from the upload response
When it happens
Trigger: Calling asset link (onBeforeLink) with a livePhotoVideoId that was deleted, never uploaded, or belongs to a different Immich server/instance.
Common situations: Mobile client uploads a live photo pair where the video part failed to upload; user deleted the motion video but the client still references its ID; syncing clients with stale IDs after a database restore.
Understand the failure class
Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.
Related errors
- Asset not found
- Asset not found
- Asset not found or asset is not a video
- Both assets must exist
- Cannot unlink Android motion photos
AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15).
Data as JSON: /api/errors/1df72b73bfb5a5a7.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/utils/asset.util.ts:151
if (timelineEnabled && !partner.inTimeline) {
continue;
}
partnerIds.add(partner.sharedById);
}
return [...partnerIds];
};
export type AssetHookRepositories = { asset: AssetRepository; event: EventRepository };
export const onBeforeLink = async (
{ asset: assetRepository, event: eventRepository }: AssetHookRepositories,
{ userId, livePhotoVideoId }: { userId: string; livePhotoVideoId: string },
) => {
const motionAsset = await assetRepository.getById(livePhotoVideoId);
if (!motionAsset) {
throw new BadRequestException('Live photo video not found');
}
if (motionAsset.type !== AssetType.Video) {
throw new BadRequestException('Live photo video must be a video');
}
if (motionAsset.ownerId !== userId) {
throw new BadRequestException('Live photo video does not belong to the user');
}
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 },
) => {View on GitHub (pinned to e55ac299a4)