{"record":{"id":"1df72b73bfb5a5a7","repo":"immich-app/immich","slug":"live-photo-video-not-found","errorCode":null,"errorMessage":"Live photo video not found","messagePattern":"Live photo video not found","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"server/src/utils/asset.util.ts","lineNumber":151,"sourceCode":"    if (timelineEnabled && !partner.inTimeline) {\n      continue;\n    }\n\n    partnerIds.add(partner.sharedById);\n  }\n\n  return [...partnerIds];\n};\n\nexport type AssetHookRepositories = { asset: AssetRepository; event: EventRepository };\n\nexport const onBeforeLink = async (\n  { asset: assetRepository, event: eventRepository }: AssetHookRepositories,\n  { userId, livePhotoVideoId }: { userId: string; livePhotoVideoId: string },\n) => {\n  const motionAsset = await assetRepository.getById(livePhotoVideoId);\n  if (!motionAsset) {\n    throw new BadRequestException('Live photo video not found');\n  }\n  if (motionAsset.type !== AssetType.Video) {\n    throw new BadRequestException('Live photo video must be a video');\n  }\n  if (motionAsset.ownerId !== userId) {\n    throw new BadRequestException('Live photo video does not belong to the user');\n  }\n\n  if (motionAsset && motionAsset.visibility === AssetVisibility.Timeline) {\n    await assetRepository.update({ id: livePhotoVideoId, visibility: AssetVisibility.Hidden });\n    await eventRepository.emit('AssetHide', { assetId: motionAsset.id, userId });\n  }\n};\n\nexport const onBeforeUnlink = async (\n  { asset: assetRepository }: AssetHookRepositories,\n  { livePhotoVideoId }: { livePhotoVideoId: string },\n) => {","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/utils/asset.util.ts#L133-L169","documentation":"Thrown by onBeforeLink in server/src/utils/asset.util.ts when a user attempts to link a Live Photo's still image to a motion video asset, but no asset exists in the database for the supplied livePhotoVideoId. It is a NestJS BadRequestException (HTTP 400), surfaced through asset update calls. The lookup is done via assetRepository.getById(livePhotoVideoId).","triggerScenarios":"PUT/PATCH /assets/:id (AssetService.update) with dto.livePhotoVideoId set to a value that does not match any asset row, or POST /assets (asset-media upload) passing a livePhotoVideoId that was never persisted (e.g. the motion-video upload failed or returned an id the client fabricated).","commonSituations":"Client sends a stale or fabricated livePhotoVideoId after the companion motion video upload failed/timed out; race where the motion asset was deleted between upload and link; test/seed data referencing an id that no longer exists.","solutions":["Verify the motion video asset was uploaded successfully and capture its real id before linking it as livePhotoVideoId.","Confirm livePhotoVideoId belongs to the same user and still exists (e.g. GET /assets/:id) before submitting the link request.","If the asset was deleted, re-upload the motion video and use the new id."],"exampleFix":"// before\nawait updateAsset({ id, livePhotoVideoId: someId }); // someId may be stale\n\n// after\nconst motion = await getAsset(motionId);\nif (!motion) { throw new Error('motion asset missing, re-upload'); }\nawait updateAsset({ id, livePhotoVideoId: motion.id });","handlingStrategy":"validation","validationCode":"// before linking, confirm the motion asset exists for this user\nconst motion = await assetRepository.getById(livePhotoVideoId);\nif (!motion) { /* surface a friendly error instead of throwing deep in onBeforeLink */ }\n","typeGuard":"// runtime guard for the link payload\nconst isLivePhotoLinkDto = (v: any): v is { livePhotoVideoId: string } =>\n  typeof v?.livePhotoVideoId === 'string' && v.livePhotoVideoId.length > 0;","tryCatchPattern":"// catch BadRequestException around the update call and map 'Live photo video not found' to a retry-upload UX\ntry { await updateAsset({ id, livePhotoVideoId }); }\ncatch (e) { if (e instanceof BadRequestException && /Live photo video not found/.test(e.message)) { await reuploadMotionVideo(); } else throw e; }","preventionTips":["Always upload the motion video first and persist its returned id before issuing the link request.","In the client, disable the 'link live photo' action until a valid motion asset id is confirmed.","Add an integration test that links only freshly-uploaded motion ids."],"tags":["live-photo","asset","validation","nestjs","bad-request"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}