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
- Filter the target list to exclude the source id before calling copy
- Add a UI guard that disables 'copy to self'
- 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
- Filter target lists to exclude the source id before calling copy
- Disable 'copy to self' in the UI
- Validate sourceId !== targetId client-side and skip silently if equal
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
- Both assets must exist
- May not request original file
- Asset not found or asset is not a video
- Invalid assetId for feature face or asset is offline
- Asset does not have valid dimensions
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/e1999cd0dcdf28ae.
Report an issue: GitHub.