{"record":{"id":"34e3a18d59d53712","repo":"github/spec-kit","slug":"catalog-payload-must-be-a-json-object","errorCode":null,"errorMessage":"Catalog payload must be a JSON object.","messagePattern":"Catalog payload must be a JSON object\\.","errorType":"exception","errorClass":"BundlerError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/bundler/models/catalog.py","lineNumber":217,"sourceCode":"        )\n\n    def with_provenance(self, source: CatalogSource) -> \"CatalogEntry\":\n        return CatalogEntry(\n            id=self.id, name=self.name, version=self.version, role=self.role,\n            description=self.description, author=self.author, license=self.license,\n            download_url=self.download_url,\n            requires_speckit_version=self.requires_speckit_version,\n            sha256=self.sha256,\n            provides=self.provides, repository=self.repository, tags=self.tags,\n            verified=self.verified, source_id=source.id,\n            source_policy=source.install_policy,\n        )\n\n\ndef load_catalog_payload(data: Any) -> dict[str, CatalogEntry]:\n    \"\"\"Parse a catalog JSON payload into ``{bundle_id: CatalogEntry}``.\"\"\"\n    if not isinstance(data, dict):\n        raise BundlerError(\"Catalog payload must be a JSON object.\")\n    bundles_raw = data.get(\"bundles\")\n    if not isinstance(bundles_raw, dict):\n        raise BundlerError(\"Catalog payload is missing a 'bundles' object.\")\n    entries: dict[str, CatalogEntry] = {}\n    for bundle_id, entry_raw in bundles_raw.items():\n        key = str(bundle_id)\n        entry = CatalogEntry.from_dict(entry_raw)\n        # The enclosing key is the authoritative bundle id used by\n        # search/resolve/install. Reject entries whose own ``id`` is missing or\n        # disagrees with the key, so a malformed or malicious catalog can't list\n        # an id that resolves to a different (or no) bundle.\n        if not entry.id:\n            raise BundlerError(\n                f\"Catalog entry for '{key}' is missing its 'id' field.\"\n            )\n        if entry.id != key:\n            raise BundlerError(\n                f\"Catalog entry id mismatch: key '{key}' != entry id \"","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/bundler/models/catalog.py#L199-L235","documentation":"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.","triggerScenarios":"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.","commonSituations":"Pointing a catalog source at the wrong endpoint (e.g. a registry listing API); misconfigured mirrors; test fixtures written as arrays; CDN rewriting responses.","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."],"exampleFix":"# before (catalog file)\n[{\"id\": \"a\", ...}, {\"id\": \"b\", ...}]\n\n# after (catalog file)\n{\"bundles\": {\"a\": {\"id\": \"a\", ...}, \"b\": {\"id\": \"b\", ...}}}","handlingStrategy":"validation","validationCode":"import json\n\ndef fetch_and_validate_catalog(url: str) -> dict:\n    data = json.loads(http_get_text(url))\n    if not isinstance(data, dict):\n        raise ValueError(f\"catalog at {url} is not a JSON object\")\n    return data","typeGuard":"def is_catalog_object(data: object) -> bool:\n    return isinstance(data, dict)","tryCatchPattern":"try:\n    load_catalog_payload(data)\nexcept BundlerError as e:\n    if \"must be a JSON object\" in str(e):\n        disable_source_and_alert(url)  # remote serves garbage","preventionTips":["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."],"tags":["bundler","catalog","validation","payload","untrusted-input"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}