roboflow/supervision · error · ValueError

'class_id' has entries but detections has - the two must…

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.

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.

Appendix: 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)