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
- 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
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
- 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
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
- Invalid environment variables:
- Invalid environment variables: \n
- Invalid system config:
- Invalid system config
- Invalid value for 'size
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)