{"record":{"id":"31c449b9c44e6f13","repo":"github/spec-kit","slug":"provides-kind-must-be-a-list-when-present","errorCode":null,"errorMessage":"provides.{kind} must be a list when present.","messagePattern":"provides\\.(.+?) must be a list when present\\.","errorType":"exception","errorClass":"BundlerError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/bundler/models/manifest.py","lineNumber":257,"sourceCode":"def _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),\n                source=(str(item[\"source\"]).strip() if item.get(\"source\") else None),\n                priority=priority,\n                strategy=(str(item[\"strategy\"]).strip() if item.get(\"strategy\") else None),\n            )\n        )\n    return refs\n\n","sourceCodeStart":239,"sourceCodeEnd":275,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/bundler/models/manifest.py#L239-L275","documentation":"Raised by the manifest helper _parse_refs when a 'provides.<kind>' entry (e.g. provides.skills) is present but is not a YAML list. Each kind under 'provides' must be a list of component mappings; a null/absent kind yields no refs, but a scalar or mapping is malformed.","triggerScenarios":"A manifest contains 'provides: {skills: {id: my-skill}}' (a single mapping) or 'provides: {skills: my-skill}' (a scalar); _parse_refs(kind, raw) rejects the non-list before iterating entries.","commonSituations":"Providing a single component and omitting the '-' bullet; mixing up the per-kind list shape with the outer 'provides' mapping shape.","solutions":["Make each provides.<kind> value a list of component mappings.","Remove the kind key if that kind provides nothing."],"exampleFix":"# before (bundle.yml)\nprovides:\n  skills:\n    id: my-skill\n\n# after\nprovides:\n  skills:\n    - id: my-skill","handlingStrategy":"validation","validationCode":"def provides_kind_lists_ok(data: dict) -> bool:\n    provides = data.get(\"provides\") or {}\n    return all(v is None or isinstance(v, list) for v in provides.values())","typeGuard":"def is_component_ref_list(raw: object) -> bool:\n    return raw is None or (isinstance(raw, list) and all(isinstance(i, dict) for i in raw))","tryCatchPattern":"try:\n    manifest = BundleManifest.from_file(p)\nexcept BundlerError as e:\n    if \"must be a list when present\" in str(e):\n        # wrap the single mapping entry in a list\n        ...","preventionTips":["Each provides.<kind> is a list, even with one component.","Keep the outer mapping / inner list distinction straight when authoring."],"tags":["bundler","manifest","yaml","validation","provides"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}