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
- Recompute the replacement array from the same filter mask used for the detections slice.
- Never overwrite `.class_id` with arrays from a different-length source; rebuild a new `Detections` via `dataclasses.replace` or a connector instead.
- 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
- After any manual field assignment, assert alignment with len(detections).
- Prefer rebuilding Detections (dataclasses.replace / connector) over overwriting fields.
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
- '{CLASS_NAME_DATA_FIELD}' has {len(class_names)} entries but
- The number of labels ({len(labels)}) does not match the numb
- Length of color lookup {len(color_lookup)} does not match le
- The number of icon paths provided ({len(icon_path)}) does no
- The length of custom_values must match the number of detecti
AI-assisted analysis of roboflow/supervision@7f254d9784 (2026-08-15).
Data as JSON: /api/errors/6ab4182b610f22d5.
Report an issue: GitHub.