chroma-core/chroma · error · TypeError
Knn limit must be a positive integer
Error message
Knn limit must be a positive integer
What it means
Thrown client-side by the Chroma JS client when building a KNN rank expression: normalizeKnnOptions (rank.ts:384) validates that Knn()'s `limit` option, which defaults to 128, is an integer greater than zero. The limit becomes the `limit` field of the {$knn: ...} JSON sent to the server, so zero, negative, fractional, NaN, or Infinity values are rejected before any network call is made. It is a TypeError, not a server response.
Source
Thrown at clients/new-js/packages/chromadb/src/execution/expression/rank.ts:385
if (Array.isArray(vector)) {
return vector.slice();
}
return Array.from(vector as Iterable<number>, (value) => {
if (
typeof value !== "number" ||
Number.isNaN(value) ||
!Number.isFinite(value)
) {
throw new TypeError("Dense query vector values must be finite numbers");
}
return value;
});
};
const normalizeKnnOptions = (options: KnnOptions): KnnOptionsNormalized => {
const limit = options.limit ?? 128;
if (!Number.isInteger(limit) || limit <= 0) {
throw new TypeError("Knn limit must be a positive integer");
}
const queryInput = options.query;
let query: number[] | SparseVector | string;
if (typeof queryInput === "string") {
query = queryInput;
} else if (
isPlainObject(queryInput) &&
Array.isArray((queryInput as SparseVector).indices) &&
Array.isArray((queryInput as SparseVector).values)
) {
const sparse = queryInput as SparseVector;
query = {
indices: sparse.indices.slice(),
values: sparse.values.slice(),
};
} else {View on GitHub (pinned to aecdd12c8a)
Solutions
- Set limit to a positive integer (>= 1) or omit it entirely to use the default of 128
- If limit is dynamic, coerce and clamp before calling: limit = Math.max(1, Math.trunc(limit))
- Validate external config before use: reject unless typeof x === 'number' && Number.isInteger(x) && x > 0
Example fix
// before
const rank = Knn({ query: embedding, limit: Number(input.limit) }); // 0 or NaN throws
// after
const raw = Number(input.limit);
const limit = Number.isInteger(raw) && raw > 0 ? raw : undefined; // undefined -> default 128
const rank = Knn({ query: embedding, limit }); Defensive patterns
Strategy: validation
Validate before calling
const safeLimit = (v: unknown): number | undefined =>
typeof v === 'number' && Number.isInteger(v) && v > 0 ? v : undefined;
const rank = Knn({ query: vec, limit: safeLimit(userLimit) }); // undefined -> default 128 Type guard
const isPositiveInteger = (v: unknown): v is number => typeof v === 'number' && Number.isInteger(v) && v > 0;
Try / catch
try {
const rank = Knn({ query: vec, limit });
} catch (e) {
if (e instanceof TypeError && e.message.includes('Knn limit')) {
return Knn({ query: vec }); // retry with default limit
}
throw e;
} Prevention
- Validate limits from env/query strings: Number('') === 0 and Number('abc') === NaN both throw inside Knn
- Omit limit to accept the default of 128
- Clamp dynamic values: Math.max(1, Math.trunc(limit))
When it happens
Trigger: Calling Knn({ query: vector, limit: 0 }), limit: -5, limit: 10.5, limit: NaN, or limit: Infinity. Typical real trigger: limit derived from unvalidated input, e.g. Number(process.env.KNN_LIMIT) when the var is empty (Number('') === 0) or non-numeric (NaN), or a limit computed as a float like n / 2 with odd n.
Common situations: Pagination limits read from env vars or query strings without validation; spreading user-supplied options objects into Knn(); porting code from an API where limit 0 meant 'use default'; limits computed dynamically (division, averaging) that yield non-integers.
Related errors
- Knn key must be a string or Key instance
- Rrf k must be a positive integer
- Knn default must be a finite number
- ${context} must be a rank expression
- Sum requires at least one rank expression
AI-assisted analysis of chroma-core/chroma@aecdd12c8a (2026-08-16).
Data as JSON: /api/errors/337b1990a7b135ff.
Report an issue: GitHub.