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
- Ensure the catalog document is a top-level JSON object: {"bundles": {...}}.
- Verify the url actually serves a catalog document (curl it and inspect the first character — should be '{').
- Fix local catalog fixtures to wrap entries under a 'bundles' object.
- 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
- curl new catalog urls before registering them and confirm a top-level object.
- Prefer https urls from trusted publishers; catalogs are untrusted input.
- Wrap catalog fetching with a validator in your own code so bad endpoints fail early.
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
- Catalog entry '{entry_id}': 'tags' must be a list of strings
- Catalog entry '{entry_id}': 'verified' must be a boolean (tr
- Each catalog entry must be a mapping.
- Catalog entry '{entry_id or '<unknown>'}': 'requires' must b
- Catalog entry '{entry_id or '<unknown>'}': 'provides' must b
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/34e3a18d59d53712.
Report an issue: GitHub.