{"record":{"id":"f2c5bd8cda0310f9","repo":"immich-app/immich","slug":"both-assets-must-exist","errorCode":null,"errorMessage":"Both assets must exist","messagePattern":"Both assets must exist","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"server/src/services/asset.service.ts","lineNumber":200,"sourceCode":"\n  async copy(\n    auth: AuthDto,\n    {\n      sourceId,\n      targetId,\n      albums = true,\n      sidecar = true,\n      sharedLinks = true,\n      stack = true,\n      favorite = true,\n    }: AssetCopyDto,\n  ) {\n    await this.requireAccess({ auth, permission: Permission.AssetCopy, ids: [sourceId, targetId] });\n    const sourceAsset = await this.assetRepository.getForCopy(sourceId);\n    const targetAsset = await this.assetRepository.getForCopy(targetId);\n\n    if (!sourceAsset || !targetAsset) {\n      throw new BadRequestException('Both assets must exist');\n    }\n\n    if (sourceId === targetId) {\n      throw new BadRequestException('Source and target id must be distinct');\n    }\n\n    if (albums) {\n      await this.albumRepository.copyAlbums({ sourceAssetId: sourceId, targetAssetId: targetId });\n    }\n\n    if (sharedLinks) {\n      await this.sharedLinkAssetRepository.copySharedLinks({ sourceAssetId: sourceId, targetAssetId: targetId });\n    }\n\n    if (stack) {\n      await this.copyStack({ sourceAsset, targetAsset });\n    }\n","sourceCodeStart":182,"sourceCodeEnd":218,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/services/asset.service.ts#L182-L218","documentation":"Thrown by AssetService.copyAssetMetadata when either getForCopy(sourceId) or getForCopy(targetId) returns null. requireAccess for AssetCopy on both ids already passed, so this typically means one or both assets were deleted between the access check and the fetch, or the ids are wrong. BadRequestException (HTTP 400).","triggerScenarios":"POST /assets/copy (or copy endpoint) supplying a sourceId or targetId that does not exist, or that is deleted concurrently with the copy call.","commonSituations":"Bulk copy where one asset in the batch was removed; cross-album copy with a stale id; client constructing target ids incorrectly.","solutions":["Confirm both source and target assets exist (GET /assets) before calling copy","Filter out ids missing from the current asset list before a bulk copy","Handle 400 by skipping the pair and continuing the batch"],"exampleFix":"// before\nawait sdk.copyAsset(sourceId, targetId, opts);\n// after\nconst [src, tgt] = await Promise.all([sdk.getAsset(sourceId), sdk.getAsset(targetId)]);\nif (!src || !tgt) throw new Error('source or target missing');\nawait sdk.copyAsset(sourceId, targetId, opts);","handlingStrategy":"validation","validationCode":"// Confirm both source and target exist before copying\nconst [src, tgt] = await Promise.all([sdk.getAsset(sourceId), sdk.getAsset(targetId)]);\nif (!src || !tgt) {\n  throw new Error('source or target asset is missing');\n}\nawait sdk.copyAsset(sourceId, targetId, opts);","typeGuard":"function bothExist<T>(a: T | null, b: T | null): a is T {\n  return a !== null && b !== null;\n}","tryCatchPattern":null,"preventionTips":["Filter bulk copy batches against the current asset list first","Handle 400 by skipping the missing pair and continuing the batch","Avoid deleting assets mid-batch while a copy job is running"],"tags":["asset","copy","not-found","validation","nestjs"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}