{"record":{"id":"8bb4e3838a846e70","repo":"immich-app/immich","slug":"invalid-value-for-numresults-numresults","errorCode":null,"errorMessage":"Invalid value for 'numResults': ${numResults}","messagePattern":"Invalid value for 'numResults': (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/repositories/search.repository.ts","lineNumber":355,"sourceCode":"    params: [DummyValue.UUID],\n  })\n  async getEmbedding(assetId: string) {\n    return this.db.selectFrom('smart_search').selectAll().where('assetId', '=', assetId).executeTakeFirst();\n  }\n\n  @GenerateSql({\n    params: [\n      {\n        userIds: [DummyValue.UUID],\n        embedding: DummyValue.VECTOR,\n        numResults: 10,\n        maxDistance: 0.6,\n      },\n    ],\n  })\n  searchFaces({ clusterGroupId, embedding, numResults, maxDistance, hasPerson, minBirthDate }: FaceEmbeddingSearch) {\n    if (!z.int().min(1).max(1000).safeParse(numResults).success) {\n      throw new Error(`Invalid value for 'numResults': ${numResults}`);\n    }\n\n    return this.db.transaction().execute(async (trx) => {\n      await sql`set local vchordrq.probes = ${sql.lit(probes[VectorIndex.Face])}`.execute(trx);\n      return await trx\n        .with('cte', (qb) =>\n          qb\n            .selectFrom('asset_face')\n            .innerJoin('asset', 'asset.id', 'asset_face.assetId')\n            .innerJoin('face_search', 'face_search.faceId', 'asset_face.id')\n            .select([\n              'asset_face.id',\n              'asset_face.personGroupId',\n              sql<number>`face_search.embedding <=> ${embedding}`.as('distance'),\n            ])\n            .where('asset.ownerId', 'in', (eb) =>\n              eb.selectFrom('user').select('user.id').where('user.clusterGroupId', '=', clusterGroupId),\n            )","sourceCodeStart":337,"sourceCodeEnd":373,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/server/src/repositories/search.repository.ts#L337-L373","documentation":"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.","triggerScenarios":"Calling searchFaces with numResults = 0, negative, > 1000, fractional, or undefined/NaN (e.g. a FaceEmbeddingSearch built from unvalidated client input).","commonSituations":"Face search endpoints receiving missing limit params, ML pipelines passing float results-per-page, or callers requesting very large result sets.","solutions":["Validate numResults is an integer 1-1000 before calling searchFaces","Clamp the value: Math.min(1000, Math.max(1, numResults))","Default to a fixed value (e.g. 100) when the field is missing","Surface a 400 BadRequest at the API layer via a shared zod schema"],"exampleFix":"// before\nawait searchFaces({ clusterGroupId, embedding, numResults: dto.limit, maxDistance, hasPerson, minBirthDate });\n// after\nconst numResults = Math.min(1000, Math.max(1, Math.trunc(dto.limit ?? 100)));\nawait searchFaces({ clusterGroupId, embedding, numResults, maxDistance, hasPerson, minBirthDate });","handlingStrategy":"validation","validationCode":"const parsed = z.int().min(1).max(1000).safeParse(numResults);\nif (!parsed.success) throw new BadRequestException('numResults must be an integer 1-1000');","typeGuard":"function isValidNumResults(v: unknown): v is number {\n  return typeof v === 'number' && Number.isInteger(v) && v >= 1 && v <= 1000;\n}","tryCatchPattern":"try {\n  return await searchFaces(faceSearch);\n} catch (e) {\n  if ((e as Error).message.startsWith(\"Invalid value for 'numResults'\")) {\n    return res.status(400).json({ error: 'numResults must be an integer between 1 and 1000' });\n  }\n  throw e;\n}","preventionTips":["Clamp numResults before invoking vector search","Share one zod schema between API validation and repository checks","Guard against undefined by defaulting numResults (e.g. 100)","Ensure ML callers pass integers, not floats"],"tags":["validation","zod","face-search"],"backgroundTag":"value-out-of-range","analyzedSha":"e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}