immich-app/immich · error · BadRequestException
Both assets must exist
Error message
Both assets must exist
What it means
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).
Source
Thrown at server/src/services/asset.service.ts:200
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 199723261c)
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
Example fix
// before
await sdk.copyAsset(sourceId, targetId, opts);
// after
const [src, tgt] = await Promise.all([sdk.getAsset(sourceId), sdk.getAsset(targetId)]);
if (!src || !tgt) throw new Error('source or target missing');
await sdk.copyAsset(sourceId, targetId, opts); Defensive patterns
Strategy: validation
Validate before calling
// Confirm both source and target exist before copying
const [src, tgt] = await Promise.all([sdk.getAsset(sourceId), sdk.getAsset(targetId)]);
if (!src || !tgt) {
throw new Error('source or target asset is missing');
}
await sdk.copyAsset(sourceId, targetId, opts); Type guard
function bothExist<T>(a: T | null, b: T | null): a is T {
return a !== null && b !== null;
} Prevention
- 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
When it happens
Trigger: POST /assets/copy (or copy endpoint) supplying a sourceId or targetId that does not exist, or that is deleted concurrently with the copy call.
Common situations: Bulk copy where one asset in the batch was removed; cross-album copy with a stale id; client constructing target ids incorrectly.
Related errors
- Asset not found or asset is not a video
- Source and target id must be distinct
- May not request original file
- Asset media not found
- Asset not found
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/f2c5bd8cda0310f9.
Report an issue: GitHub.