bmad-code-org/BMAD-METHOD · error · RenderError

{label} must be a list, got {type(value).__name__}

Error message

{label} must be a list, got {type(value).__name__}

What it means

`_require_string_list` validates values that the renderer expects to be a list of strings (e.g. multi-item customization defaults). If the resolved value is not a Python `list` — it is a single string, a number, a bool, a dict, or None — it raises naming the label and the actual type. This is the typed-list counterpart of `_require_string` and is reached from `_resolve_customization_value` for list-typed defaults and from `_require_string_list` callers.

Source

Thrown at src/scripts/render_skill.py:65

    current: Any = data
    for part in dotted_path.split("."):
        if not isinstance(current, dict) or part not in current:
            raise RenderError(f"missing {label} `{dotted_path}`")
        current = current[part]
    return current


def _require_string(value: Any, label: str, *, allow_empty: bool = False) -> str:
    if not isinstance(value, str):
        raise RenderError(f"{label} must be a string, got {type(value).__name__}")
    if not allow_empty and not value.strip():
        raise RenderError(f"{label} must not be empty")
    return value


def _require_string_list(value: Any, label: str) -> list[str]:
    if not isinstance(value, list):
        raise RenderError(f"{label} must be a list, got {type(value).__name__}")
    result = []
    for index, item in enumerate(value):
        result.append(_require_string(item, f"{label}[{index}]"))
    return result


def _require_review_layers(value: Any, label: str) -> list[dict[str, str]]:
    if not isinstance(value, list):
        raise RenderError(f"{label} must be a list of tables")
    result: list[dict[str, str]] = []
    seen: set[str] = set()
    for index, item in enumerate(value):
        item_label = f"{label}[{index}]"
        if not isinstance(item, dict):
            raise RenderError(f"{item_label} must be a table")
        identifier = _require_string(item.get("id"), f"{item_label}.id")
        if identifier in seen:
            raise RenderError(f"duplicate review layer id `{identifier}`")

View on GitHub (pinned to b70486b9bd)

Solutions

  1. Wrap the value in an array: `tags = ["x"]` not `tags = "x"`.
  2. If the field should accept a single string, change the consuming default/template to a scalar path (but the renderer currently routes lists through this validator, so prefer the array).
  3. Confirm the override layer did not replace an array with a scalar during merge.
  4. Validate the structure: `python -c "import tomllib;print(type(tomllib.load(open('f','rb'))['k']))."`.

Example fix

# before (customize.toml)
[workflow]
extra_steps = "deploy"     # string, but renderer wants a list

# after
[workflow]
extra_steps = ["deploy"]
Defensive patterns

Strategy: type-guard

Validate before calling

def list_fields_are_lists(d, list_fields):
    return all(isinstance(d[f], list) for f in list_fields if f in d)

Type guard

def is_str_list(v: object) -> bool:
    return isinstance(v, list) and all(isinstance(x, str) for x in v)

Try / catch

from render_skill import RenderError
try:
    _require_string_list(value, label)
except RenderError as e:
    print(f"error: {e}", file=sys.stderr); sys.exit(2)

Prevention

When it happens

Trigger: A customization default expects `["a","b"]` but the override provides a single string `"a"`; a value written as an inline table instead of an array; a scalar where the template iterates; None from a missing optional layer.

Common situations: TOML author writes a single value where a list is required; a merge replaced an array with a scalar; a default was changed from scalar to list but the override was not updated.

Related errors


AI-assisted analysis of bmad-code-org/BMAD-METHOD@b70486b9bd (2026-08-13). Data as JSON: /api/errors/0d7dc67ee088ffb5. Report an issue: GitHub.