immich-app/immich · error · NotFoundException

Asset not found

Error message

Asset not found

What it means

Thrown by PersonService.createFace when the asset referenced by dto.assetId does not resolve through assetRepository.getById (with edits + exifInfo). The permission checks for AssetUpdate and PersonRead already passed, so this is purely a data-existence failure. NotFoundException -> HTTP 404.

Source

Thrown at server/src/services/person.service.ts:620

  private findOrFail(id: string) {
    return findOrFail(() => this.personRepository.getById(id), 'Person');
  }

  // TODO return a asset face response
  async createFace(auth: AuthDto, dto: AssetFaceCreateDto): Promise<void> {
    await Promise.all([
      this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: [dto.assetId] }),
      this.requireAccess({ auth, permission: Permission.PersonRead, ids: [dto.personId] }),
    ]);

    const [asset, person] = await Promise.all([
      this.assetRepository.getById(dto.assetId, { edits: true, exifInfo: true }),
      this.findOrFail(dto.personId),
    ]);

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

    const edits = asset.edits || [];

    let topLeft: Point = { x: dto.x, y: dto.y };
    let bottomRight: Point = { x: dto.x + dto.width, y: dto.y + dto.height };

    // the coordinates received from the client are based on the edited preview image
    // we need to convert them to the coordinate space of the original unedited image
    if (edits.length > 0) {
      if (!asset.width || !asset.height || !asset.exifInfo?.exifImageWidth || !asset.exifInfo?.exifImageHeight) {
        throw new BadRequestException('Asset does not have valid dimensions');
      }

      // convert from preview to full dimensions
      const scaleFactor = asset.width / dto.imageWidth;
      topLeft = { x: topLeft.x * scaleFactor, y: topLeft.y * scaleFactor };
      bottomRight = { x: bottomRight.x * scaleFactor, y: bottomRight.y * scaleFactor };

View on GitHub (pinned to 199723261c)

Solutions

  1. Refresh the asset list before letting the user tag a face.
  2. Confirm dto.assetId is a valid, existing asset via GET /assets/{id} before submitting.
  3. Handle 404 by dropping the local face-tag attempt.

Example fix

// before
if (!asset) {
  throw new NotFoundException('Asset not found');
}

// after (the access check should have caught this; surface the precise reason)
if (!asset) {
  throw new NotFoundException(`Asset ${dto.assetId} not found`);
}
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the asset exists before tagging a face on it.
const asset = await assetService.getById(auth, dto.assetId).catch(() => null);
if (!asset) {
  throw new Error(`Refusing face-tag: asset ${dto.assetId} does not exist`);
}
await personService.createFace(auth, dto);

Type guard

const isAsset = (a: AssetResponseDto | null | undefined): a is AssetResponseDto =>
  !!a && typeof a.id === 'string';

Try / catch

try {
  await personService.createFace(auth, dto);
} catch (e) {
  if (e instanceof NotFoundException && /Asset/.test(e.message)) {
    // drop the local face-tag attempt and refresh the asset list
  }
  throw e;
}

Prevention

When it happens

Trigger: POST /people/{personId}/faces (or the face-create endpoint) with an assetId that was deleted, never existed, or is malformed.

Common situations: Stale asset id in client state after deletion; race between delete and face-create; passing an encoded vs decoded UUID; asset belongs to a different visibility tier.

Related errors


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