immich-app/immich · error · BadRequestException

Asset not found

Error message

Asset not found

What it means

Thrown by AssetService.getOne when assetRepository.getById returns null after requireAccess already confirmed the user has AssetRead permission. Because the access check passed, the most likely cause is a race: the asset was deleted between the access check and the detail fetch. Returned as BadRequestException (HTTP 400), not 404.

Source

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

    const stats = await this.assetRepository.getStatistics(auth.user.id, dto);
    return mapStats(stats);
  }

  async get(auth: AuthDto, id: string): Promise<AssetResponseDto | SanitizedAssetResponseDto> {
    await this.requireAccess({ auth, permission: Permission.AssetRead, ids: [id] });

    const asset = await this.assetRepository.getById(id, {
      exifInfo: true,
      owner: true,
      faces: { person: true },
      stack: { assets: true },
      edits: true,
      tags: true,
    });

    if (!asset) {
      throw new BadRequestException('Asset not found');
    }

    if (auth.sharedLink && !auth.sharedLink.showExif) {
      return mapAsset(asset, { stripMetadata: true, withStack: true, auth });
    }

    const data = mapAsset(asset, { withStack: true, auth });

    if (auth.sharedLink) {
      delete data.owner;
    }

    if (data.ownerId !== auth.user.id || auth.sharedLink) {
      data.people = [];
    }

    return data;
  }

View on GitHub (pinned to 199723261c)

Solutions

  1. Refresh the asset list after any delete to remove stale ids
  2. Treat 400 'Asset not found' as removal and drop the asset from local state
  3. Avoid concurrent delete+view of the same asset
Defensive patterns

Strategy: try-catch

Try / catch

try {
  return await sdk.getAsset(id);
} catch (e) {
  if (e instanceof HttpError && e.status === 400 && /Asset not found/.test(e.message)) {
    ui.removeAsset(id); // stale reference, drop it
    return null;
  }
  throw e;
}

Prevention

When it happens

Trigger: GET /assets/:id that passes access control but finds no row on the detailed getById (with exifInfo, owner, faces, stack, edits, tags relations). Concurrent delete is the classic trigger.

Common situations: Timeline still showing an asset after it was deleted from another client; long-lived detail view while a cleanup job runs; optimistic UI not refreshing after delete.

Related errors


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