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
- Resolve both ids first (GET /api/assets/:id) and confirm both return 200 before copying.
- Re-select the target asset from a fresh asset list if the old id is stale.
- Ensure both assets belong to the same Immich instance and are visible to the current auth context.
- 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
- Resolve both ids immediately before copying; never use long-lived ids
- Ensure source and target come from the same instance/auth context
- Re-select targets from fresh album listings
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
- Asset not found
- Asset not found
- Asset not found or asset is not a video
- Live photo video not found
- Asset media not found
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)