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
- Wrap the value in an array: `tags = ["x"]` not `tags = "x"`.
- 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).
- Confirm the override layer did not replace an array with a scalar during merge.
- 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
- Write multi-value fields as TOML arrays, even single-element ones.
- Keep override layers type-consistent with their defaults.
- Validate array-typed config in CI.
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
- {label} must be a string, got {type(value).__name__}
- {label} must not be empty
- keyed array identifier `{candidate}` must be a string, got {
- keyed array identifier `{candidate}` must not be empty
- missing {label} `{dotted_path}`
AI-assisted analysis of bmad-code-org/BMAD-METHOD@b70486b9bd (2026-08-13).
Data as JSON: /api/errors/0d7dc67ee088ffb5.
Report an issue: GitHub.