chroma-core/chroma · error · ChromaValueError

Expected 'whereDocument' operand for ${operator} to be a lis

Error message

Expected 'whereDocument' operand for ${operator} to be a list with at least two 'whereDocument' expressions

What it means

$and and $or require a list with at least two whereDocument expressions (utils.ts:687-691); a conjunction or disjunction of zero or one clause is rejected by design. After the length check, each element is re-validated recursively, so nested clauses get the same one-operator rule.

Source

Thrown at clients/new-js/packages/chromadb/src/utils.ts:688

      "$not_regex",
      "$and",
      "$or",
    ].includes(operator)
  ) {
    throw new ChromaValueError(
      `Expected 'whereDocument' operator to be one of $contains, $not_contains, $matches, $not_matches, $regex, $not_regex, $and, or $or, but got ${operator}`,
    );
  }

  if (operator === "$and" || operator === "$or") {
    if (!Array.isArray(operand)) {
      throw new ChromaValueError(
        `Expected operand for ${operator} to be a list of 'whereDocument' expressions, but got ${operand}`,
      );
    }

    if (operand.length <= 1) {
      throw new ChromaValueError(
        `Expected 'whereDocument' operand for ${operator} to be a list with at least two 'whereDocument' expressions`,
      );
    }

    operand.forEach((item) => validateWhereDocument(item));
  }

  if (
    (operand === "$contains" ||
      operand === "$not_contains" ||
      operand === "$regex" ||
      operand === "$not_regex") &&
    (typeof (operator as any) !== "string" || operator.length === 0)
  ) {
    throw new ChromaValueError(
      `Expected operand for ${operator} to be a non empty string, but got ${operand}`,
    );
  }

View on GitHub (pinned to aecdd12c8a)

Solutions

  1. If only one predicate remains, pass it directly instead of wrapping it in $and/$or
  2. If zero predicates remain, omit the whereDocument parameter entirely
  3. Guard dynamic builders: predicates.length >= 2 ? { $and: predicates } : predicates[0]

Example fix

// before
const wd = { $and: predicates }; // predicates may have 0 or 1 items

// after
const wd = predicates.length >= 2 ? { $and: predicates } : predicates[0];
Defensive patterns

Strategy: validation

Validate before calling

function buildWhereDocument(predicates: Record<string, string>[]): Record<string, unknown> | undefined {
  if (predicates.length === 0) return undefined;          // omit filter entirely
  if (predicates.length === 1) return predicates[0];       // single clause, no wrapper
  return { $and: predicates };                             // two or more
}

Type guard

const isValidCompound = (w: { $and?: unknown[] } | { $or?: unknown[] }) => {
  const list = (w as { $and?: unknown[] }).$and ?? (w as { $or?: unknown[] }).$or;
  return list === undefined || (Array.isArray(list) && list.length >= 2);
};

Try / catch

try {
  await col.get({ whereDocument: { $and: predicates } });
} catch (e) {
  if (e instanceof Error && e.message.includes('at least two')) {
    const collapsed = predicates.length === 1 ? predicates[0] : undefined;
    if (collapsed) await col.get({ whereDocument: collapsed });
  } else throw e;
}

Prevention

When it happens

Trigger: whereDocument: { $and: [] }; { $or: [{ $contains: 'x' }] } (single element); typically from collection.get()/query()/delete() where the $and array was built dynamically and ended up with 0 or 1 predicates.

Common situations: Programmatically composing $and from a runtime predicate list (e.g. search filters where the user filled in none or only one field) — the list length drops below two.

Related errors


AI-assisted analysis of chroma-core/chroma@aecdd12c8a (2026-08-16). Data as JSON: /api/errors/23db6991a0cec7bc. Report an issue: GitHub.