github/spec-kit · error · BundlerError

Catalog entry '{entry_id or '<unknown>'}': 'provides' must b

Error message

Catalog entry '{entry_id or '<unknown>'}': 'provides' must be a mapping when present.

What it means

Raised by CatalogEntry.from_dict() when an entry's 'provides' field is present but is not a mapping. 'provides' declares capabilities the bundle supplies with integer weights (e.g. {"lint": 1}); the same no-`or {}` discipline as 'requires' applies so corrupt falsy values cannot masquerade as 'provides nothing'.

Source

Thrown at src/specify_cli/bundler/models/catalog.py:176

        if not isinstance(data, dict):
            raise BundlerError("Each catalog entry must be a mapping.")
        entry_id = str(data.get("id", "")).strip()
        # `or {}` would coerce a FALSY non-mapping (0, '', False, []) to {} before
        # the isinstance guard, silently accepting a corrupt catalog entry; only
        # an absent/None value means "not present".
        requires = data.get("requires")
        if requires is None:
            requires = {}
        elif not isinstance(requires, dict):
            raise BundlerError(
                f"Catalog entry '{entry_id or '<unknown>'}': 'requires' must be a "
                "mapping when present."
            )
        provides_raw = data.get("provides")
        if provides_raw is None:
            provides_raw = {}
        elif not isinstance(provides_raw, dict):
            raise BundlerError(
                f"Catalog entry '{entry_id or '<unknown>'}': 'provides' must be a "
                "mapping when present."
            )
        return cls(
            id=entry_id,
            name=str(data.get("name", "")).strip(),
            version=str(data.get("version", "")).strip(),
            role=str(data.get("role", "")).strip(),
            description=str(data.get("description", "")).strip(),
            author=str(data.get("author", "")).strip(),
            license=str(data.get("license", "")).strip(),
            download_url=str(data.get("download_url", "")).strip(),
            requires_speckit_version=str(requires.get("speckit_version", "")).strip(),
            sha256=(
                None
                if data.get("sha256") is None
                else str(data["sha256"]).strip()
            ),

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Express 'provides' as an object mapping capability names to integers: {"lint": 1}.
  2. If the bundle provides nothing declared, omit the key or set null.
  3. Align with the documented catalog schema before publishing.

Example fix

# before (catalog JSON)
"provides": ["lint"]

# after (catalog JSON)
"provides": {"lint": 1}
Defensive patterns

Strategy: type-guard

Validate before calling

provides = entry.get("provides")
if provides is not None and not isinstance(provides, dict):
    raise ValueError("'provides' must be a mapping when present")

Type guard

def is_valid_provides(value: object) -> bool:
    return value is None or isinstance(value, dict)

Try / catch

try:
    CatalogEntry.from_dict(entry_raw)
except BundlerError as e:
    if "'provides' must be a mapping" in str(e):
        entry_raw.pop("provides", None)  # drop malformed capability claims

Prevention

When it happens

Trigger: A catalog entry with "provides": "lint" or "provides": ["lint"]; provides: 0 or false; provides: null is fine (treated as absent).

Common situations: Catalog authors writing provides as a tag list; schema drift; hand-edited catalogs approximating the format from memory.

Related errors


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