ultralytics/ultralytics · critical · SyntaxError

{dataset} key missing ❌. either 'names' or 'nc' are require

Error message

{dataset} key missing ❌.
 either 'names' or 'nc' are required in all data YAMLs.

What it means

Raised by check_det_dataset when the data YAML declares neither 'names' nor 'nc'. At least one is mandatory: names (list or dict of class names) or nc (integer class count, from which placeholder names class_0..class_{nc-1} are generated). A bare 'names:' that parses to None counts as missing, which is why the check uses `is None` rather than key membership.

Source

Thrown at ultralytics/data/utils.py:522

    # Read YAML
    data = YAML.load(file, append_filename=True)  # dictionary

    # Checks
    for k in "train", "val":
        if k not in data:
            if k != "val" or "validation" not in data:
                raise SyntaxError(
                    emojis(f"{dataset} '{k}:' key missing ❌.\n'train' and 'val' are required in all data YAMLs.")
                )
            LOGGER.warning("renaming data YAML 'validation' key to 'val' to match YOLO format.")
            data["val"] = data.pop("validation")  # replace 'validation' key with 'val' key
    if split and not data.get(split):
        raise FileNotFoundError(f"{dataset} '{split}:' images not found ❌")
    # `names` compared to None, not membership: a bare `names:` parses to None and len(None) below
    # raises. `nc` stays membership so a valueless `nc:` still reaches its "must be an integer" error.
    if data.get("names") is None and "nc" not in data:
        raise SyntaxError(emojis(f"{dataset} key missing ❌.\n either 'names' or 'nc' are required in all data YAMLs."))
    if "nc" in data and not isinstance(data["nc"], int):
        try:
            nc = float(data["nc"])  # accept integer-like values, e.g. '10' or 10.0, but not 1.9 or placeholders
            if nc != int(nc):
                raise ValueError
            data["nc"] = int(nc)
        except (TypeError, ValueError):
            raise SyntaxError(emojis(f"{dataset} 'nc: {data['nc']}' must be an integer ❌."))
    if data.get("names") is not None and data.get("nc") is not None and len(data["names"]) != data["nc"]:
        raise SyntaxError(emojis(f"{dataset} 'names' length {len(data['names'])} and 'nc: {data['nc']}' must match."))
    if data.get("names") is None:
        data["names"] = [f"class_{i}" for i in range(data["nc"])]
    else:
        data["nc"] = len(data["names"])

    data["names"] = check_class_names(data["names"])
    data["channels"] = data.get("channels", 3)  # get image channels, default to 3

View on GitHub (pinned to 0449ea011c)

Solutions

  1. Add a names mapping, e.g. 'names:\n 0: person\n 1: car'
  2. Or add 'nc: <int>' to get auto-generated placeholder names class_0..class_{nc-1}
  3. If names was already written, verify it is not empty/null in the parsed YAML (print(YAML.load(...)))
  4. Check indentation — entries under names: must be indented one level

Example fix

# before (my.yaml)
train: images/train
val: images/val

# after
train: images/train
val: images/val
names:
  0: cat
  1: dog
Defensive patterns

Strategy: validation

Validate before calling

from ultralytics.utils import YAML

def validate_classes(path: str) -> dict:
    data = YAML.load(path)
    if data.get('names') is None and 'nc' not in data:
        raise ValueError(f"{path} must define either 'names:' or 'nc:'")
    return data

Prevention

When it happens

Trigger: train(data='my.yaml') where my.yaml has train:/val: but no names/nc; a YAML with 'names:' written but no classes under it (parses to None); commenting out names while leaving nc out.

Common situations: Truncated or template YAMLs; users who assume class names come from the model checkpoint instead of the dataset YAML; indentation bugs that orphan the names entries.

Related errors


AI-assisted analysis of ultralytics/ultralytics@0449ea011c (2026-08-15). Data as JSON: /api/errors/68d45b6790867853. Report an issue: GitHub.