github/spec-kit · error · BundlerError

Each catalog entry must be a mapping.

Error message

Each catalog entry must be a mapping.

What it means

Raised by CatalogEntry.from_dict() when an element under a catalog's 'bundles' object is not a mapping. Each value in the bundles map must itself be a JSON object describing the bundle; scalars, lists, or nulls fail this guard before any field checks run.

Source

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

    role: str
    description: str
    author: str
    license: str
    download_url: str
    requires_speckit_version: str
    sha256: str | None = None
    provides: dict[str, int] = field(default_factory=dict)
    repository: str | None = None
    tags: tuple[str, ...] = ()
    verified: bool = False
    # Resolution provenance (filled in by the catalog stack at lookup time):
    source_id: str | None = None
    source_policy: InstallPolicy | None = None

    @classmethod
    def from_dict(cls, data: Any) -> "CatalogEntry":
        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 "

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Make each bundles value a full entry object with at least id (matching the key), name, version, role, and the other expected fields.
  2. Fix the catalog generator to emit objects, not scalars.
  3. Validate fetched catalogs with a schema check before load_catalog_payload() in test harnesses.

Example fix

# before (catalog JSON)
{"bundles": {"my-bundle": "1.0.0"}}

# after (catalog JSON)
{"bundles": {"my-bundle": {"id": "my-bundle", "name": "My Bundle", "version": "1.0.0", "role": "command", "description": "", "author": "", "license": "", "download_url": "https://..."}}}
Defensive patterns

Strategy: type-guard

Validate before calling

for key, raw in payload.get("bundles", {}).items():
    if not isinstance(raw, dict):
        raise ValueError(f"bundle '{key}' entry is not an object")

Type guard

def is_entry_mapping(value: object) -> bool:
    return isinstance(value, dict)

Try / catch

try:
    CatalogEntry.from_dict(entry_raw)
except BundlerError as e:
    if "must be a mapping" in str(e):
        skip_and_report_entry(bundle_id, e)

Prevention

When it happens

Trigger: A catalog payload like {"bundles": {"my-bundle": "1.0.0"}} where the value is a bare version string; a null value from sparse JSON; a producer that inlined a list of files as the entry.

Common situations: Hand-authored catalogs misunderstanding the schema; catalogs generated by scripts mapping bundle ids to version strings; JSON merge leaving null placeholders.

Related errors


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