immich-app/immich · error · Error

errorMessage || error

Error message

errorMessage || error

What it means

Thrown by the duplicates resolution UI (Svelte route +page.svelte) when the server's resolveDuplicates call returns success=false for a group. The message is errorMessage || error from the response, so it forwards whatever the backend reported. This is a client-side re-throw to trigger the surrounding error/notification handler.

Source

Thrown at web/src/routes/(user)/utilities/duplicates/[[photos=photos]]/[[assetId=id]]/+page.svelte:114

  };

  const handleResolve = async (duplicateId: string, duplicateAssetIds: string[], trashIds: string[]) => {
    const forceDelete = !featureFlagsManager.value.trash;
    const shouldConfirmDelete = trashIds.length > 0 && forceDelete;

    return withConfirmation(
      async () => {
        const keepAssetIds = duplicateAssetIds.filter((id) => !trashIds.includes(id));

        const response = await resolveDuplicates({
          duplicateResolveDto: {
            groups: [{ duplicateId, keepAssetIds, trashAssetIds: trashIds }],
          },
        });

        const { success, error, errorMessage } = response[0];
        if (!success) {
          throw new Error(errorMessage || error);
        }

        duplicates = duplicates.filter((duplicate) => duplicate.duplicateId !== duplicateId);

        deletedNotification(trashIds.length);
        await navigateToIndex(duplicatesIndex);
      },
      shouldConfirmDelete ? $t('delete_duplicates_confirmation') : undefined,
      shouldConfirmDelete ? $t('permanently_delete') : undefined,
    );
  };

  const handleStack = async (duplicateId: string, assets: AssetResponseDto[]) => {
    const assetIds = assets.map((asset) => asset.id);
    await createStack({ stackCreateDto: { assetIds } });
    await updateAssets({ assetBulkUpdateDto: { ids: assetIds, duplicateId: null } });
    duplicates = duplicates.filter((duplicate) => duplicate.duplicateId !== duplicateId);
    await navigateToIndex(duplicatesIndex);

View on GitHub (pinned to 199723261c)

Solutions

  1. Refresh the duplicates list and retry — the group may have changed.
  2. Inspect the response.error/errorMessage in DevTools for the specific backend reason.
  3. Re-run the duplicate detection job if the list is stale/inconsistent.
  4. Ensure the user has AssetUpdate/AssetDelete permission on all assets in the group.

Example fix

// before
const { success, error, errorMessage } = response[0];
if (!success) throw new Error(errorMessage || error);

// after — clearer user-facing message
if (!success) throw new Error(errorMessage || error || $t('errors.unable_to_resolve_duplicates'));
Defensive patterns

Strategy: try-catch

Validate before calling

// re-fetch the group to ensure assets still exist before resolving
const group = await getDuplicateGroup(duplicateId);
if (!group || group.assetIds.some(id => !knownAssets.has(id))) { refreshDuplicates(); return; }

Type guard

const isResolveSuccess = (r: { success?: boolean }): boolean => Boolean(r?.success);

Try / catch

try { await resolveDuplicates({ duplicateResolveDto }); }
catch (e) { if (/duplicate/i.test(e.message)) { await refreshDuplicates(); } else throw e; }

Prevention

When it happens

Trigger: User clicks resolve-keep/trash in the Duplicates utility; the backend resolveDuplicates RPC returns { success: false, error/errorMessage } for the duplicateId group (e.g. assets no longer present, permission denied, concurrent modification).

Common situations: Assets in the duplicate group were deleted or moved between the duplicate-scan job and the user resolving them; permission changes; another client resolved the same group first; backend validation failure.

Related errors


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