chroma-core/chroma · error · ValueError

Expected metadata key to be a str, got {key}

Error message

Expected metadata key to be a str, got {key}

What it means

Update-path metadata keys must be str, enforced by validate_update_metadata (chromadb/api/types.py:1117) on collection.update. It raises a ValueError here, whereas the insert-path twin raises TypeError - worth knowing if you catch exceptions by type. Numeric or other non-str keys from dict(zip(...)), numeric-keyed YAML, or programmatically built dicts trigger it.

Source

Thrown at chromadb/api/types.py:1117

            raise ValueError(
                f"Expected metadata value to be a str, int, float, bool, SparseVector, list, or None, got {value} which is a {type(value).__name__}"
            )
    return metadata


def validate_update_metadata(metadata: UpdateMetadata) -> UpdateMetadata:
    """Validates metadata to ensure it is a dictionary of strings to strings, ints, floats, bools, SparseVectors, or lists thereof"""
    if not isinstance(metadata, dict) and metadata is not None:
        raise ValueError(
            f"Expected metadata to be a dict or None, got {type(metadata)}"
        )
    if metadata is None:
        return metadata
    if len(metadata) == 0:
        raise ValueError(f"Expected metadata to be a non-empty dict, got {metadata}")
    for key, value in metadata.items():
        if not isinstance(key, str):
            raise ValueError(f"Expected metadata key to be a str, got {key}")
        # Check if value is a SparseVector (validation happens in __post_init__)
        if isinstance(value, SparseVector):
            pass  # Already validated in SparseVector.__post_init__
        elif isinstance(value, list):
            _validate_metadata_list_value(key, value)
        # isinstance(True, int) evaluates to True, so we need to check for bools separately
        elif not isinstance(value, bool) and not isinstance(
            value, (str, int, float, type(None))
        ):
            raise ValueError(
                f"Expected metadata value to be a str, int, float, bool, SparseVector, list, or None, got {value}"
            )
    return metadata


def serialize_metadata(metadata: Optional[Metadata]) -> Optional[Dict[str, Any]]:
    """Serialize metadata for transport, converting SparseVector dataclass instances to dicts.

View on GitHub (pinned to aecdd12c8a)

Solutions

  1. Stringify keys when building the update dict: {str(k): v for k, v in meta.items()}
  2. Catch ValueError (not TypeError) around update() in defensive wrappers
  3. Keep metadata schemas keyed by strings from the start

Example fix

# before
patch = dict(zip(field_codes, new_values))  # {101: 'x'}

# after
patch = {str(k): v for k, v in zip(field_codes, new_values)}
Defensive patterns

Strategy: type-guard

Validate before calling

patch = {str(k) if not isinstance(k, str) else k: v for k, v in patch.items()}
collection.update(ids=ids, metadatas=[patch])

Type guard

def update_metadata_keys_are_str(meta) -> bool:
    return all(isinstance(k, str) for k in meta)

Try / catch

try:
    collection.update(ids=ids, metadatas=metas)
except ValueError as e:  # update path raises ValueError, insert path TypeError
    if 'Expected metadata key to be a str' in str(e):
        collection.update(ids=ids, metadatas=[{str(k): v for k, v in m.items()} for m in metas])
    else:
        raise

Prevention

When it happens

Trigger: collection.update(ids=..., metadatas=[{1: 'a'}]); metadata dicts built with enumerate() integers as keys; YAML-parsed configs where '1:' becomes an int key; id-to-value maps misused directly as metadata.

Common situations: Programmatically built update dicts; mappings from numeric codes to values; lenient parsers preserving numeric key types.

Related errors


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