immich-app/immich · error · BadRequestException

Both assets must exist

Error message

Both assets must exist

What it means

copy duplicates metadata (albums, tags, etc.) from a source asset to a target asset. It fetches both via getForCopy and requires both to resolve; if either id is missing or inaccessible it throws BadRequestException('Both assets must exist') before any copying happens.

Solutions

  1. Resolve both ids first (GET /api/assets/:id) and confirm both return 200 before copying.
  2. Re-select the target asset from a fresh asset list if the old id is stale.
  3. Ensure both assets belong to the same Immich instance and are visible to the current auth context.
  4. Handle the error by surfacing 'source or target no longer exists' to the user and aborting the copy.

Example fix

// before
await api.copyAssets(sourceId, maybeDeletedTargetId);
// after
const [s, t] = await Promise.all([api.getAssetInfo(sourceId), api.getAssetInfo(targetId)].map((p) => p.catch(() => null)));
if (s && t) await api.copyAssets(sourceId, targetId);
else throw new Error('Source or target asset no longer exists');
Defensive patterns

Strategy: validation

Validate before calling

const [s, t] = await Promise.all([api.getAssetInfo(sourceId), api.getAssetInfo(targetId)].map((p) => p.catch(() => null)));
if (!s || !t) throw new Error('Both source and target must exist before copy');

Try / catch

try { await api.copyAssets(sourceId, targetId); } catch (e) { if (String(e).includes('Both assets must exist')) abortCopyAndRefresh(); else throw e; }

Prevention

When it happens

Trigger: POST /api/assets/copy (or the copy mutation) where sourceId or targetId does not exist, was deleted, or is not visible to the authenticated user / shared link.

Common situations: Client UIs allowing copy between albums after one asset was trashed; scripts passing placeholder or cross-instance ids; duplicated-asset workflows racing with trash emptying.

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


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/f2c5bd8cda0310f9. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/asset.service.ts:205

  async copy(
    auth: AuthDto,
    {
      sourceId,
      targetId,
      albums = true,
      sidecar = true,
      sharedLinks = true,
      stack = true,
      favorite = true,
    }: AssetCopyDto,
  ) {
    await this.requireAccess({ auth, permission: Permission.AssetCopy, ids: [sourceId, targetId] });
    const sourceAsset = await this.assetRepository.getForCopy(sourceId);
    const targetAsset = await this.assetRepository.getForCopy(targetId);

    if (!sourceAsset || !targetAsset) {
      throw new BadRequestException('Both assets must exist');
    }

    if (sourceId === targetId) {
      throw new BadRequestException('Source and target id must be distinct');
    }

    if (albums) {
      await this.albumRepository.copyAlbums({ sourceAssetId: sourceId, targetAssetId: targetId });
    }

    if (sharedLinks) {
      await this.sharedLinkAssetRepository.copySharedLinks({ sourceAssetId: sourceId, targetAssetId: targetId });
    }

    if (stack) {
      await this.copyStack({ sourceAsset, targetAsset });
    }

View on GitHub (pinned to e55ac299a4)