github/spec-kit · error · BundlerError

Each provides.{kind} entry must be a mapping.

Error message

Each provides.{kind} entry must be a mapping.

What it means

Raised by the manifest helper _parse_refs when an entry inside a 'provides.<kind>' list is not a YAML mapping. Each entry must be a mapping carrying id, and optionally version, source, priority, and strategy — the component-ref contract.

Source

Thrown at src/specify_cli/bundler/models/manifest.py:261

    character-by-character) and any non-list/tuple, matching the manifest
    contract (``string[]``).
    """
    if raw is None:
        return ()
    if isinstance(raw, (str, bytes)) or not isinstance(raw, (list, tuple)):
        raise BundlerError(f"'{field_name}' must be a list of strings when present.")
    return tuple(str(item) for item in raw)


def _parse_refs(kind: str, raw: Any) -> list[ComponentRef]:
    if raw is None:
        return []
    if not isinstance(raw, list):
        raise BundlerError(f"provides.{kind} must be a list when present.")
    refs: list[ComponentRef] = []
    for item in raw:
        if not isinstance(item, dict):
            raise BundlerError(f"Each provides.{kind} entry must be a mapping.")
        priority = _parse_priority(kind, item.get("priority"))
        refs.append(
            ComponentRef(
                kind=kind,
                id=_text(item.get("id")),
                version=(str(item["version"]).strip() if item.get("version") else None),
                source=(str(item["source"]).strip() if item.get("source") else None),
                priority=priority,
                strategy=(str(item["strategy"]).strip() if item.get("strategy") else None),
            )
        )
    return refs


def _parse_priority(kind: str, raw: Any) -> int | None:
    if raw is None:
        return None
    if isinstance(raw, bool) or not isinstance(raw, (int, str)):

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Wrap each entry in a mapping with at least an 'id' key.
  2. Keep every entry in the list the same mapping shape.

Example fix

# before (bundle.yml)
provides:
  skills:
    - my-skill

# after
provides:
  skills:
    - id: my-skill
Defensive patterns

Strategy: type-guard

Validate before calling

def provides_entries_are_mappings(data: dict) -> bool:
    provides = data.get("provides") or {}
    return all(
        isinstance(item, dict)
        for lst in provides.values()
        if isinstance(lst, list)
        for item in lst
    )

Type guard

def is_component_ref(item: object) -> bool:
    return isinstance(item, dict) and bool(str(item.get("id", "")).strip())

Try / catch

try:
    manifest = BundleManifest.from_file(p)
except BundlerError as e:
    if "entry must be a mapping" in str(e):
        # rewrite '- my-skill' as '- id: my-skill'
        ...

Prevention

When it happens

Trigger: A provides list contains a bare string entry, e.g. 'provides: {skills: [my-skill]}', or a nested list. _parse_refs iterates and rejects the first non-dict item.

Common situations: Shorthand single-name lists (natural but unsupported); mixing string IDs and mapping entries in the same list.

Related errors


AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14). Data as JSON: /api/errors/bb121ebf20b6dad3. Report an issue: GitHub.