{"record":{"id":"96fddc78fba7ad93","repo":"bmad-code-org/BMAD-METHOD","slug":"label-must-be-a-string-got-type-value-name","errorCode":null,"errorMessage":"{label} must be a string, got {type(value).__name__}","messagePattern":"(.+?) must be a string, got (.+?)","errorType":"validation","errorClass":"RenderError","httpStatus":null,"severity":"error","filePath":"src/scripts/render_skill.py","lineNumber":57,"sourceCode":"\ndef _canonical_json(value: Any) -> bytes:\n    return json.dumps(\n        value, ensure_ascii=False, sort_keys=True, separators=(\",\", \":\")\n    ).encode(\"utf-8\")\n\n\ndef _lookup(data: dict[str, Any], dotted_path: str, label: str) -> Any:\n    current: Any = data\n    for part in dotted_path.split(\".\"):\n        if not isinstance(current, dict) or part not in current:\n            raise RenderError(f\"missing {label} `{dotted_path}`\")\n        current = current[part]\n    return current\n\n\ndef _require_string(value: Any, label: str, *, allow_empty: bool = False) -> str:\n    if not isinstance(value, str):\n        raise RenderError(f\"{label} must be a string, got {type(value).__name__}\")\n    if not allow_empty and not value.strip():\n        raise RenderError(f\"{label} must not be empty\")\n    return value\n\n\ndef _require_string_list(value: Any, label: str) -> list[str]:\n    if not isinstance(value, list):\n        raise RenderError(f\"{label} must be a list, got {type(value).__name__}\")\n    result = []\n    for index, item in enumerate(value):\n        result.append(_require_string(item, f\"{label}[{index}]\"))\n    return result\n\n\ndef _require_review_layers(value: Any, label: str) -> list[dict[str, str]]:\n    if not isinstance(value, list):\n        raise RenderError(f\"{label} must be a list of tables\")\n    result: list[dict[str, str]] = []","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/bmad-code-org/BMAD-METHOD/blob/b70486b9bdcb0a404d329e2a763b57964e7f1360/src/scripts/render_skill.py#L39-L75","documentation":"`_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.","triggerScenarios":"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.","commonSituations":"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.","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\"`."],"exampleFix":"# before (config.toml)\n[project]\nport = 8080          # int\nflag = true          # bool\n\n# after\n[project]\nport = \"8080\"\nflag = \"true\"","handlingStrategy":"type-guard","validationCode":"def all_scalar_values_are_str(d, prefix=''):\n    bad = []\n    for k,v in d.items():\n        p = f'{prefix}.{k}' if prefix else k\n        if isinstance(v, dict):\n            bad += all_scalar_values_are_str(v, p)\n        elif not isinstance(v, list) and not isinstance(v, str):\n            bad.append(p)\n    return bad","typeGuard":"def is_str_or_list(v: object) -> bool:\n    return isinstance(v, str) or isinstance(v, list)","tryCatchPattern":"from render_skill import RenderError\ntry:\n    _require_string(value, label)\nexcept RenderError as e:\n    print(f\"error: {e}\", file=sys.stderr); sys.exit(2)","preventionTips":["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."],"tags":["render","config","type-mismatch","validation","bmad"],"backgroundTag":null,"analyzedSha":"b70486b9bdcb0a404d329e2a763b57964e7f1360","analyzedAt":"2026-08-13T01:21:12.247Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}