{"record":{"id":"0412df4f52b42ea1","repo":"chroma-core/chroma","slug":"expected-metadata-to-be-a-non-empty-dict-got-met","errorCode":null,"errorMessage":"Expected metadata to be a non-empty dict, got {metadata}","messagePattern":"Expected metadata to be a non-empty dict, got (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"chromadb/api/types.py","lineNumber":1114,"sourceCode":"        elif not isinstance(value, bool) and not isinstance(\n            value, (str, int, float, type(None))\n        ):\n            raise ValueError(\n                f\"Expected metadata value to be a str, int, float, bool, SparseVector, list, or None, got {value} which is a {type(value).__name__}\"\n            )\n    return metadata\n\n\ndef validate_update_metadata(metadata: UpdateMetadata) -> UpdateMetadata:\n    \"\"\"Validates metadata to ensure it is a dictionary of strings to strings, ints, floats, bools, SparseVectors, or lists thereof\"\"\"\n    if not isinstance(metadata, dict) and metadata is not None:\n        raise ValueError(\n            f\"Expected metadata to be a dict or None, got {type(metadata)}\"\n        )\n    if metadata is None:\n        return metadata\n    if len(metadata) == 0:\n        raise ValueError(f\"Expected metadata to be a non-empty dict, got {metadata}\")\n    for key, value in metadata.items():\n        if not isinstance(key, str):\n            raise ValueError(f\"Expected metadata key to be a str, got {key}\")\n        # Check if value is a SparseVector (validation happens in __post_init__)\n        if isinstance(value, SparseVector):\n            pass  # Already validated in SparseVector.__post_init__\n        elif isinstance(value, list):\n            _validate_metadata_list_value(key, value)\n        # isinstance(True, int) evaluates to True, so we need to check for bools separately\n        elif not isinstance(value, bool) and not isinstance(\n            value, (str, int, float, type(None))\n        ):\n            raise ValueError(\n                f\"Expected metadata value to be a str, int, float, bool, SparseVector, list, or None, got {value}\"\n            )\n    return metadata\n\n","sourceCodeStart":1096,"sourceCodeEnd":1132,"githubUrl":"https://github.com/chroma-core/chroma/blob/aecdd12c8a891610db8653630b066b32ceb678b5/chromadb/api/types.py#L1096-L1132","documentation":"validate_update_metadata rejects {} as a per-record metadata on update (chromadb/api/types.py:1114, reached from collection.update via chromadb/segment.py:407). An empty update dict is treated as an invalid input rather than 'change nothing'. The correct way to express 'nothing to change' for a record is to omit it from the ids/metadatas lists, or use None as the element.","triggerScenarios":"collection.update(ids=['a'], metadatas=[{}]); batch updates where some rows have no changed fields; diff-based update builders that emit {} for unchanged rows.","commonSituations":"Change-data-capture pipelines computing per-row diffs and producing empty dicts for unchanged rows; UI save handlers that send the whole object including empty metadata; generic update loops over all rows regardless of change.","solutions":["Skip unchanged rows: only include ids whose metadata diff is non-empty","Send None instead of {}: metadatas=[m if m else None for m in metadatas]","Split the batch so only genuinely changed records are updated"],"exampleFix":"# before\ncollection.update(ids=all_ids, metadatas=[diff(r) for r in rows])  # some diffs are {}\n\n# after\nchanged = [(i, d) for i, d in zip(all_ids, diffs) if d]\ncollection.update(ids=[i for i, _ in changed], metadatas=[d for _, d in changed])","handlingStrategy":"validation","validationCode":"pairs = [(i, m) for i, m in zip(ids, metadatas) if m]\nif pairs:\n    collection.update(ids=[i for i, _ in pairs], metadatas=[m for _, m in pairs])","typeGuard":"def has_no_empty_update_dicts(metadatas) -> bool:\n    return all(m is None or len(m) > 0 for m in metadatas)","tryCatchPattern":"try:\n    collection.update(ids=ids, metadatas=metas)\nexcept ValueError as e:\n    if 'non-empty dict' in str(e):\n        collection.update(ids=ids, metadatas=[m if m else None for m in metas])\n    else:\n        raise","preventionTips":["Update only rows with a non-empty diff; skip unchanged rows","Use None per record to leave metadata untouched","In CDC pipelines, drop no-op changes before batching"],"tags":["chromadb","python","metadata","update","empty-dict","validation"],"backgroundTag":"empty-metadata-dict","analyzedSha":"aecdd12c8a891610db8653630b066b32ceb678b5","analyzedAt":"2026-08-16T21:53:27.228Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}