immich-app/immich · error

Invalid value for 'numResults

Error message

Invalid value for 'numResults': ${numResults}

What it means

searchFaces validates numResults with zod (z.int().min(1).max(1000)) before executing the face-embedding vector search. It throws when numResults is not an integer in [1,1000], guarding the SQL vector limit and DB resources.

Solutions

  1. Validate numResults is an integer 1-1000 before calling searchFaces
  2. Clamp the value: Math.min(1000, Math.max(1, numResults))
  3. Default to a fixed value (e.g. 100) when the field is missing
  4. Surface a 400 BadRequest at the API layer via a shared zod schema

Example fix

// before
await searchFaces({ clusterGroupId, embedding, numResults: dto.limit, maxDistance, hasPerson, minBirthDate });
// after
const numResults = Math.min(1000, Math.max(1, Math.trunc(dto.limit ?? 100)));
await searchFaces({ clusterGroupId, embedding, numResults, maxDistance, hasPerson, minBirthDate });
Defensive patterns

Strategy: validation

Validate before calling

const parsed = z.int().min(1).max(1000).safeParse(numResults);
if (!parsed.success) throw new BadRequestException('numResults must be an integer 1-1000');

Type guard

function isValidNumResults(v: unknown): v is number {
  return typeof v === 'number' && Number.isInteger(v) && v >= 1 && v <= 1000;
}

Try / catch

try {
  return await searchFaces(faceSearch);
} catch (e) {
  if ((e as Error).message.startsWith("Invalid value for 'numResults'")) {
    return res.status(400).json({ error: 'numResults must be an integer between 1 and 1000' });
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling searchFaces with numResults = 0, negative, > 1000, fractional, or undefined/NaN (e.g. a FaceEmbeddingSearch built from unvalidated client input).

Common situations: Face search endpoints receiving missing limit params, ML pipelines passing float results-per-page, or callers requesting very large result sets.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/8bb4e3838a846e70. Report an issue: GitHub.

Appendix: source

Thrown at server/src/repositories/search.repository.ts:355

    params: [DummyValue.UUID],
  })
  async getEmbedding(assetId: string) {
    return this.db.selectFrom('smart_search').selectAll().where('assetId', '=', assetId).executeTakeFirst();
  }

  @GenerateSql({
    params: [
      {
        userIds: [DummyValue.UUID],
        embedding: DummyValue.VECTOR,
        numResults: 10,
        maxDistance: 0.6,
      },
    ],
  })
  searchFaces({ clusterGroupId, embedding, numResults, maxDistance, hasPerson, minBirthDate }: FaceEmbeddingSearch) {
    if (!z.int().min(1).max(1000).safeParse(numResults).success) {
      throw new Error(`Invalid value for 'numResults': ${numResults}`);
    }

    return this.db.transaction().execute(async (trx) => {
      await sql`set local vchordrq.probes = ${sql.lit(probes[VectorIndex.Face])}`.execute(trx);
      return await trx
        .with('cte', (qb) =>
          qb
            .selectFrom('asset_face')
            .innerJoin('asset', 'asset.id', 'asset_face.assetId')
            .innerJoin('face_search', 'face_search.faceId', 'asset_face.id')
            .select([
              'asset_face.id',
              'asset_face.personGroupId',
              sql<number>`face_search.embedding <=> ${embedding}`.as('distance'),
            ])
            .where('asset.ownerId', 'in', (eb) =>
              eb.selectFrom('user').select('user.id').where('user.clusterGroupId', '=', clusterGroupId),
            )

View on GitHub (pinned to e55ac299a4)