{"record":{"id":"b267fe19e29e9bc3","repo":"chroma-core/chroma","slug":"expected-operand-for-operator-to-be-a-non-empty","errorCode":null,"errorMessage":"Expected operand for ${operator} to be a non empty string, but got ${operand}","messagePattern":"Expected operand for (.+?) to be a non empty string, but got (.+?)","errorType":"validation","errorClass":"ChromaValueError","httpStatus":null,"severity":"error","filePath":"clients/new-js/packages/chromadb/src/utils.ts","lineNumber":703,"sourceCode":"    }\n\n    if (operand.length <= 1) {\n      throw new ChromaValueError(\n        `Expected 'whereDocument' operand for ${operator} to be a list with at least two 'whereDocument' expressions`,\n      );\n    }\n\n    operand.forEach((item) => validateWhereDocument(item));\n  }\n\n  if (\n    (operand === \"$contains\" ||\n      operand === \"$not_contains\" ||\n      operand === \"$regex\" ||\n      operand === \"$not_regex\") &&\n    (typeof (operator as any) !== \"string\" || operator.length === 0)\n  ) {\n    throw new ChromaValueError(\n      `Expected operand for ${operator} to be a non empty string, but got ${operand}`,\n    );\n  }\n};\n\n/**\n * Validates include fields for query operations.\n * @param options - Validation options\n * @param options.include - Array of fields to include in results\n * @param options.exclude - Optional array of fields that should not be included\n * @throws ChromaValueError if include fields are invalid\n */\nexport const validateInclude = ({\n  include,\n  exclude,\n}: {\n  include: Include[];\n  exclude?: Include[];","sourceCodeStart":685,"sourceCodeEnd":721,"githubUrl":"https://github.com/chroma-core/chroma/blob/aecdd12c8a891610db8653630b066b32ceb678b5/clients/new-js/packages/chromadb/src/utils.ts#L685-L721","documentation":"Intended meaning: the operand of $contains/$not_contains/$regex/$not_regex must be a non-empty string. In this client version the guard is transposed — it compares `operand` against operator names and tests typeof of `operator` (utils.ts:696-702) — so with an ordinary single-key clause it never fires; a non-string or empty-string operand passes through and fails server-side instead. The local throw is only reachable via degenerate input such as { '': '$contains' } (empty-string key whose value equals an operator name).","triggerScenarios":"Effectively only { '': '$contains' }-shaped objects trip the local throw. The intended cases — { $contains: '' } or { $regex: 123 } — are not caught here and surface later as a server error with a different message.","commonSituations":"Empty-string search terms from unvalidated user input; numeric regex patterns passed straight from JSON; noticing after an upgrade that operand type errors now come from the server rather than the client.","solutions":["Validate operands yourself before the call: non-empty string for $contains/$not_contains/$regex/$not_regex","Reject or default empty search terms before building the filter","Upgrade the chromadb JS package, whose utils.ts operand validation is actively maintained"],"exampleFix":"// before\nwhereDocument: { $contains: term } // term can be ''\n\n// after\nif (typeof term !== 'string' || term.length === 0) throw new Error('term must be a non-empty string');\nwhereDocument: { $contains: term }","handlingStrategy":"validation","validationCode":"const STRING_OPERATORS = ['$contains', '$not_contains', '$regex', '$not_regex'];\nfunction validateOperands(w: Record<string, unknown>): void {\n  const [op, val] = Object.entries(w)[0];\n  if (STRING_OPERATORS.includes(op) && (typeof val !== 'string' || val.length === 0)) {\n    throw new TypeError(`operand for ${op} must be a non-empty string`);\n  }\n}","typeGuard":"const hasNonEmptyStringOperand = (\n  w: unknown\n): w is { $contains: string } | { $not_contains: string } | { $regex: string } | { $not_regex: string } => {\n  if (typeof w !== 'object' || w === null) return false;\n  const [op, val] = Object.entries(w)[0];\n  return ['$contains', '$not_contains', '$regex', '$not_regex'].includes(op) &&\n    typeof val === 'string' && val.length > 0;\n};","tryCatchPattern":"try {\n  await col.get({ whereDocument: { $contains: term } });\n} catch (e) {\n  // this client-side guard is transposed in utils.ts, so expect the failure from the server instead:\n  if (e instanceof Error && /contains|operand/i.test(e.message)) {\n    // surface 'search term must be a non-empty string' to the caller\n  } else throw e;\n}","preventionTips":["Sanitize search input before building filters: non-empty string, trimmed","Do not rely on the client to reject empty operands in this version — validate them yourself","Pin and periodically upgrade the chromadb JS package to pick up validator fixes"],"tags":["javascript","validation","where-document","edge-case"],"backgroundTag":"invalid-query-filter","analyzedSha":"aecdd12c8a891610db8653630b066b32ceb678b5","analyzedAt":"2026-08-16T21:53:27.228Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}