immich-app/immich · warning · BadRequestException

Source and target id must be distinct

Error message

Source and target id must be distinct

What it means

Thrown by AssetService.copyAssetMetadata when sourceId === targetId. Copying an asset's metadata onto itself is a no-op and is rejected explicitly after the existence check passes. BadRequestException (HTTP 400).

Source

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

      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 199723261c)

Solutions

  1. Filter the target list to exclude the source id before calling copy
  2. Add a UI guard that disables 'copy to self'
  3. Validate sourceId !== targetId client-side before the request

Example fix

// before
await sdk.copyAsset(id, id, opts); // 400
// after
if (sourceId === targetId) return; // skip self
await sdk.copyAsset(sourceId, targetId, opts);
Defensive patterns

Strategy: validation

Validate before calling

// Exclude self from copy targets
if (sourceId === targetId) {
  throw new Error('source and target must differ');
}
await sdk.copyAsset(sourceId, targetId, opts);

// For bulk targets:
const targets = allTargetIds.filter(id => id !== sourceId);

Type guard

function areDistinct(a: string, b: string): boolean {
  return a !== b;
}

Prevention

When it happens

Trigger: POST /assets/copy where the source and target id are the same value — e.g. a UI bug that passes the same selected asset as both source and target, or a copy-to-self feature attempt.

Common situations: Drag-and-drop copy where the drop target resolves to the source; batch copy where a source id leaks into the target list; testing copy with the same id for simplicity.

Related errors


AI-assisted analysis of immich-app/immich@199723261c (2026-08-12). Data as JSON: /api/errors/e1999cd0dcdf28ae. Report an issue: GitHub.