immich-app/immich · warning · BadRequestException

Source and target id must be distinct

Error message

Source and target id must be distinct

What it means

In copy, copying an asset onto itself is rejected: after verifying both assets exist, sourceId === targetId throws BadRequestException('Source and target id must be distinct'). Self-copy is meaningless (would duplicate relations onto the same row) so it is blocked as an invalid argument.

Solutions

  1. Add a client-side check: skip/flag pairs where sourceId === targetId before calling the API.
  2. Fix the UI selection logic so a single asset cannot be both copy source and target.
  3. If you intended duplication, use the appropriate duplicate/stack operation instead of copy.

Example fix

// before
await api.copyAssets(asset.id, asset.id); // self-copy
// after
if (sourceId === targetId) return; // nothing to copy
await api.copyAssets(sourceId, targetId);
Defensive patterns

Strategy: validation

Validate before calling

if (sourceId === targetId) throw new Error('Source and target must be distinct assets');

Prevention

When it happens

Trigger: POST copy where the same asset id is passed as both sourceId and targetId — typically a client bug or a UI selection bug (same item picked twice).

Common situations: Drag-and-drop handlers attaching the same asset as both source and target; batch scripts iterating lists without checking pair distinctness; copy-paste of an id into both form fields.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

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

      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 });
    }

    if (favorite) {
      await this.assetRepository.update({ id: targetId, isFavorite: sourceAsset.isFavorite });
    }

View on GitHub (pinned to e55ac299a4)