{"record":{"id":"646525cb8fa48a67","repo":"github/spec-kit","slug":"field-name-must-be-a-list-of-strings-when-pres","errorCode":null,"errorMessage":"'{field_name}' must be a list of strings when present.","messagePattern":"'(.+?)' must be a list of strings when present\\.","errorType":"exception","errorClass":"BundlerError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/bundler/models/manifest.py","lineNumber":249,"sourceCode":"    was silently accepted and the bundle shipped ``\"None\"`` as its\n    author/license/description.\n    \"\"\"\n    if raw is None:\n        return \"\"\n    return str(raw).strip()\n\n\ndef _parse_str_list(raw: Any, field_name: str) -> tuple[str, ...]:\n    \"\"\"Coerce a manifest list-of-strings field into a tuple of strings.\n\n    Rejects a bare string/bytes (which would otherwise be iterated\n    character-by-character) and any non-list/tuple, matching the manifest\n    contract (``string[]``).\n    \"\"\"\n    if raw is None:\n        return ()\n    if isinstance(raw, (str, bytes)) or not isinstance(raw, (list, tuple)):\n        raise BundlerError(f\"'{field_name}' must be a list of strings when present.\")\n    return tuple(str(item) for item in raw)\n\n\ndef _parse_refs(kind: str, raw: Any) -> list[ComponentRef]:\n    if raw is None:\n        return []\n    if not isinstance(raw, list):\n        raise BundlerError(f\"provides.{kind} must be a list when present.\")\n    refs: list[ComponentRef] = []\n    for item in raw:\n        if not isinstance(item, dict):\n            raise BundlerError(f\"Each provides.{kind} entry must be a mapping.\")\n        priority = _parse_priority(kind, item.get(\"priority\"))\n        refs.append(\n            ComponentRef(\n                kind=kind,\n                id=_text(item.get(\"id\")),\n                version=(str(item[\"version\"]).strip() if item.get(\"version\") else None),","sourceCodeStart":231,"sourceCodeEnd":267,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/bundler/models/manifest.py#L231-L267","documentation":"Raised by the manifest helper _parse_str_list when a list-of-strings field (e.g. requires.tools, requires.mcp, or tags) is a bare string/bytes or any non-list/tuple value. The explicit string/bytes check prevents a bare string from being iterated character-by-character, which would silently produce garbage entries like 'g','i','t'.","triggerScenarios":"A manifest with 'tools: git' or 'tags: productivity' (bare strings instead of YAML lists) is parsed; _parse_str_list rejects the str before tuple(str(item) for item in raw) can iterate characters.","commonSituations":"Writing a single-item list without the '-' bullet — the most common YAML shorthand mistake; quoting a comma-separated string instead of a sequence.","solutions":["Convert the value to a YAML list, even for a single item.","Remove the key if the list should be empty (absence/null returns ())."],"exampleFix":"# before (bundle.yml)\ntools: git\n\n# after\ntools:\n  - git","handlingStrategy":"validation","validationCode":"def str_list_ok(value: object) -> bool:\n    return value is None or (\n        isinstance(value, (list, tuple))\n        and not isinstance(value, (str, bytes))\n    )","typeGuard":"def is_str_list(raw: object) -> bool:\n    return raw is None or (isinstance(raw, list) and all(isinstance(x, str) for x in raw))","tryCatchPattern":"try:\n    manifest = BundleManifest.from_file(p)\nexcept BundlerError as e:\n    if \"list of strings\" in str(e):\n        # convert bare strings (tools/tags/mcp) to single-item YAML lists\n        ...","preventionTips":["Even single items need the '- ' bullet in tools/mcp/tags.","Never pass a comma-separated string where a list is expected."],"tags":["bundler","manifest","yaml","validation","list-of-strings"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}