{"record":{"id":"af5ab6c028246641","repo":"chroma-core/chroma","slug":"expected-where-document-operand-value-for-operator-af5ab6","errorCode":null,"errorMessage":"Expected where document operand value for operator {operator} to be a non-empty str","messagePattern":"Expected where document operand value for operator (.+?) to be a non-empty str","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"chromadb/api/types.py","lineNumber":1330,"sourceCode":"            )\n        if operator == \"$and\" or operator == \"$or\":\n            if not isinstance(operand, list):\n                raise ValueError(\n                    f\"Expected document value for $and or $or to be a list of where document expressions, got {operand}\"\n                )\n            if len(operand) <= 1:\n                raise ValueError(\n                    f\"Expected document value for $and or $or to be a list with at least two where document expressions, got {operand}\"\n                )\n            for where_document_expression in operand:\n                validate_where_document(where_document_expression)\n        # Value is $contains/$not_contains/$regex/$not_regex operator\n        elif not isinstance(operand, str):\n            raise ValueError(\n                f\"Expected where document operand value for operator {operator} to be a str, got {operand}\"\n            )\n        elif len(operand) == 0:\n            raise ValueError(\n                f\"Expected where document operand value for operator {operator} to be a non-empty str\"\n            )\n\n\ndef validate_include(include: Include, dissalowed: Optional[Include] = None) -> None:\n    \"\"\"Validates include to ensure it is a list of strings. Since get does not allow distances, allow_distances is used\n    to control if distances is allowed\"\"\"\n\n    if not isinstance(include, list):\n        raise ValueError(f\"Expected include to be a list, got {include}\")\n    for item in include:\n        if not isinstance(item, str):\n            raise ValueError(f\"Expected include item to be a str, got {item}\")\n\n        # Get the valid items from the Literal type inside the List\n        valid_items = get_args(get_args(Include)[0])\n        if item not in valid_items:\n            raise ValueError(","sourceCodeStart":1312,"sourceCodeEnd":1348,"githubUrl":"https://github.com/chroma-core/chroma/blob/aecdd12c8a891610db8653630b066b32ceb678b5/chromadb/api/types.py#L1312-L1348","documentation":"validate_where_document requires the operand of $contains/$not_contains/$regex/$not_regex to be a NON-EMPTY string. An empty string (\"\") is rejected because matching an empty substring/pattern is meaningless in Chroma's document filter engine.","triggerScenarios":"collection.get(where_document={\"$contains\": \"\"}) — typically the string comes from an f-string or variable that is empty at runtime, e.g. {\"$contains\": user_query} when user_query == \"\", or a config/env value that was never set.","commonSituations":"Search-as-you-type UIs issuing a query on every keystroke, including the empty input; optional filter parameters defaulting to \"\" instead of None; stripping whitespace from user input leaving \"\".","solutions":["Skip the where_document filter entirely when the value is empty instead of sending {\"$contains\": \"\"}","Default optional filter variables to None and build the filter only if the value is truthy","For search-as-you-type, guard the request with: if not query.strip(): return []"],"exampleFix":"# before\nresults = collection.query(query_texts=[q], where_document={\"$contains\": search_term})  # search_term == \"\"\n\n# after\nif search_term:\n    results = collection.query(query_texts=[q], where_document={\"$contains\": search_term})\nelse:\n    results = collection.query(query_texts=[q], n_results=5)","handlingStrategy":"validation","validationCode":"def optional_contains(term: str | None) -> dict | None:\n    \"\"\"Return a $contains filter only for non-empty terms.\"\"\"\n    if term is None or not term.strip():\n        return None\n    return {\"$contains\": term}\n\nwd = optional_contains(user_query)\nkwargs = {\"where_document\": wd} if wd else {}\nres = collection.query(query_texts=[q], n_results=5, **kwargs)","typeGuard":"def is_non_empty_str_operand(wd: dict) -> bool:\n    if len(wd) != 1:\n        return False\n    op, operand = next(iter(wd.items()))\n    return op in (\"$contains\", \"$not_contains\", \"$regex\", \"$not_regex\") and isinstance(operand, str) and len(operand) > 0","tryCatchPattern":"try:\n    res = collection.query(query_texts=[q], where_document={\"$contains\": term}, n_results=5)\nexcept ValueError as e:\n    if \"non-empty str\" in str(e):\n        res = collection.query(query_texts=[q], n_results=5)  # retry without the filter\n    else:\n        raise","preventionTips":["Default optional filter inputs to None, never \"\"","In search-as-you-type UIs, short-circuit empty input before issuing the query","Strip and check truthiness of template variables used inside $contains"],"tags":["chromadb","where-document","empty-string","input-validation"],"backgroundTag":"query-filter-validation","analyzedSha":"aecdd12c8a891610db8653630b066b32ceb678b5","analyzedAt":"2026-08-16T21:53:27.228Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}