bmad-code-org/BMAD-METHOD · error · RenderError
{label} must be a string, got {type(value).__name__}
Error message
{label} must be a string, got {type(value).__name__} What it means
`_require_string` is the shared validator render_skill.py uses for every config/customization value that must be text. If the resolved value is not a Python `str` (it is an int, float, bool, list, dict, or None), it raises naming the label (e.g. `config.project.name`) and the actual type. TOML's typed scalars make this common: an unquoted number, a bare `true`/`false`, or an inline table where a string was expected.
Source
Thrown at src/scripts/render_skill.py:57
def _canonical_json(value: Any) -> bytes:
return json.dumps(
value, ensure_ascii=False, sort_keys=True, separators=(",", ":")
).encode("utf-8")
def _lookup(data: dict[str, Any], dotted_path: str, label: str) -> Any:
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]] = []View on GitHub (pinned to b70486b9bd)
Solutions
- Quote the value in the TOML layer so TOML yields a string: `name = "BMAD"` not `name = BMAD`.
- If a number/bool is genuinely wanted, change the consuming template to stringify it (but the renderer currently requires strings, so prefer quoting).
- Audit the merged config to find which layer introduced the non-string.
- For booleans, write the intended text explicitly: `enabled = "true"`.
Example fix
# before (config.toml) [project] port = 8080 # int flag = true # bool # after [project] port = "8080" flag = "true"
Defensive patterns
Strategy: type-guard
Validate before calling
def all_scalar_values_are_str(d, prefix=''):
bad = []
for k,v in d.items():
p = f'{prefix}.{k}' if prefix else k
if isinstance(v, dict):
bad += all_scalar_values_are_str(v, p)
elif not isinstance(v, list) and not isinstance(v, str):
bad.append(p)
return bad Type guard
def is_str_or_list(v: object) -> bool:
return isinstance(v, str) or isinstance(v, list) Try / catch
from render_skill import RenderError
try:
_require_string(value, label)
except RenderError as e:
print(f"error: {e}", file=sys.stderr); sys.exit(2) Prevention
- Quote scalar values in TOML so they parse as strings.
- Represent booleans as "true"/"false" strings when a template consumes them as text.
- Pin merge behaviour so a scalar is not silently replaced by a non-scalar.
When it happens
Trigger: A config value written as `port = 8080` where the token expects a string; `enabled = true` consumed by a template that needs text; a key set to an inline table `{ ... }` or array where a scalar string is required; a value inherited as `None` from an optional layer.
Common situations: TOML's native typing biting a template author who expected YAML-style everything-is-a-string; a customization override that changed a string default to a number; a layer merge that replaced a string with a non-string.
Related errors
- {label} must be a list, 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/96fddc78fba7ad93.
Report an issue: GitHub.