roboflow/supervision · error · ValueError

'class_id' has {len(detections.class_id)} entries but detect

Error message

'class_id' has {len(detections.class_id)} entries but detections has {len(detections)} - the two must stay aligned.

What it means

Raised while resolving label text when `detections.class_id` exists but its length differs from `len(detections)`. This indicates the internal invariant `len(class_id) == len(xyxy)` was broken, almost always by direct mutation of `detections.class_id` after construction, since the Detections dataclass normally validates alignment.

Source

Thrown at src/supervision/annotators/utils.py:285

    if custom_labels is not None:
        return custom_labels

    if CLASS_NAME_DATA_FIELD in detections.data:
        class_names = detections.data[CLASS_NAME_DATA_FIELD]
        # `Detections.data` is normally kept aligned with `xyxy` by the
        # dataclass's own validation, but a caller can bypass it by mutating
        # `detections.data` directly. Fail loudly rather than silently
        # dropping or duplicating labels.
        if len(class_names) != len(detections):
            raise ValueError(
                f"'{CLASS_NAME_DATA_FIELD}' has {len(class_names)} entries "
                f"but detections has {len(detections)} - the two must stay "
                "aligned."
            )
        return [str(v) for v in class_names]
    if detections.class_id is not None:
        if len(detections.class_id) != len(detections):
            raise ValueError(
                f"'class_id' has {len(detections.class_id)} entries but "
                f"detections has {len(detections)} - the two must stay "
                "aligned."
            )
        return [str(v) for v in detections.class_id]
    return [str(i) for i in range(len(detections))]


def snap_boxes(
    xyxy: npt.NDArray[np.float32],
    resolution_wh: tuple[int, int],
) -> npt.NDArray[np.float32]:
    """
    Shifts `label` bounding boxes into the frame so that they are fully contained
    within the given resolution, prioritizing the top/left edge.
    Unlike `clip_boxes`, this function does not crop boxes.
    It moves them entirely if they exceed the frame boundaries.

View on GitHub (pinned to 7f254d9784)

Solutions

  1. Recompute the replacement array from the same filter mask used for the detections slice.
  2. Never overwrite `.class_id` with arrays from a different-length source; rebuild a new `Detections` via `dataclasses.replace` or a connector instead.
  3. Add an assertion `assert len(detections.class_id) == len(detections)` right after any manual field assignment in your pipeline.

Example fix

# before
new_ids = np.array([0, 1, 2])          # from unfiltered result
detections = detections[:2]             # len 2
detections.class_id = new_ids           # len 3 -> ValueError at annotate time

# after
detections = detections[:2]
detections.class_id = np.array([0, 1])  # aligned with len(detections)
Defensive patterns

Strategy: validation

Validate before calling

if detections.class_id is not None:
    assert len(detections.class_id) == len(detections), "class_id misaligned"

Prevention

When it happens

Trigger: Assigning `detections.class_id = new_class_ids` where `new_class_ids` came from the unfiltered model output while `detections` was sliced; constructing `Detections(xyxy=..., class_id=...)` with mismatched array lengths is blocked by the validator, so the error surfaces when the field is replaced afterwards.

Common situations: Remapping class ids after filtering detections (e.g. merging classes) but computing the mapping array from the pre-filter result; interactive sessions that overwrite `.class_id`; copying fields between two Detections objects of different sizes.

Related errors


AI-assisted analysis of roboflow/supervision@7f254d9784 (2026-08-15). Data as JSON: /api/errors/6ab4182b610f22d5. Report an issue: GitHub.