github/spec-kit · error · BundlerError

Catalog payload must be a JSON object.

Error message

Catalog payload must be a JSON object.

What it means

Raised by load_catalog_payload() when the parsed catalog data is not a JSON object at the top level. Catalogs are expected to be a dict with (at least) a 'bundles' key; a JSON array, bare string, number, or null fails immediately before any entry parsing.

Source

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

        )

    def with_provenance(self, source: CatalogSource) -> "CatalogEntry":
        return CatalogEntry(
            id=self.id, name=self.name, version=self.version, role=self.role,
            description=self.description, author=self.author, license=self.license,
            download_url=self.download_url,
            requires_speckit_version=self.requires_speckit_version,
            sha256=self.sha256,
            provides=self.provides, repository=self.repository, tags=self.tags,
            verified=self.verified, source_id=source.id,
            source_policy=source.install_policy,
        )


def load_catalog_payload(data: Any) -> dict[str, CatalogEntry]:
    """Parse a catalog JSON payload into ``{bundle_id: CatalogEntry}``."""
    if not isinstance(data, dict):
        raise BundlerError("Catalog payload must be a JSON object.")
    bundles_raw = data.get("bundles")
    if not isinstance(bundles_raw, dict):
        raise BundlerError("Catalog payload is missing a 'bundles' object.")
    entries: dict[str, CatalogEntry] = {}
    for bundle_id, entry_raw in bundles_raw.items():
        key = str(bundle_id)
        entry = CatalogEntry.from_dict(entry_raw)
        # The enclosing key is the authoritative bundle id used by
        # search/resolve/install. Reject entries whose own ``id`` is missing or
        # disagrees with the key, so a malformed or malicious catalog can't list
        # an id that resolves to a different (or no) bundle.
        if not entry.id:
            raise BundlerError(
                f"Catalog entry for '{key}' is missing its 'id' field."
            )
        if entry.id != key:
            raise BundlerError(
                f"Catalog entry id mismatch: key '{key}' != entry id "

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Ensure the catalog document is a top-level JSON object: {"bundles": {...}}.
  2. Verify the url actually serves a catalog document (curl it and inspect the first character — should be '{').
  3. Fix local catalog fixtures to wrap entries under a 'bundles' object.
  4. If a remote source is persistently malformed, remove or replace that source.

Example fix

# before (catalog file)
[{"id": "a", ...}, {"id": "b", ...}]

# after (catalog file)
{"bundles": {"a": {"id": "a", ...}, "b": {"id": "b", ...}}}
Defensive patterns

Strategy: validation

Validate before calling

import json

def fetch_and_validate_catalog(url: str) -> dict:
    data = json.loads(http_get_text(url))
    if not isinstance(data, dict):
        raise ValueError(f"catalog at {url} is not a JSON object")
    return data

Type guard

def is_catalog_object(data: object) -> bool:
    return isinstance(data, dict)

Try / catch

try:
    load_catalog_payload(data)
except BundlerError as e:
    if "must be a JSON object" in str(e):
        disable_source_and_alert(url)  # remote serves garbage

Prevention

When it happens

Trigger: Fetching a url that returns a JSON array of entries instead of an object; a url serving a JSON error envelope like "not found"; a local file containing a serialized list; a proxy or CDN serving unexpected content that happens to parse as JSON.

Common situations: Pointing a catalog source at the wrong endpoint (e.g. a registry listing API); misconfigured mirrors; test fixtures written as arrays; CDN rewriting responses.

Related errors


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