{"record":{"id":"a89f6b579cfc6427","repo":"github/spec-kit","slug":"invalid-preset-catalog-format-from-url-expected","errorCode":null,"errorMessage":"Invalid preset catalog format from {url}: expected a JSON object","messagePattern":"Invalid preset catalog format from (.+?): expected a JSON object","errorType":"exception","errorClass":"PresetError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/presets/__init__.py","lineNumber":4245,"sourceCode":"        ``{\"presets\": []}`` or ``{\"presets\": null}`` slip through here and\n        then crash with ``AttributeError: 'list' object has no attribute\n        'items'`` deep inside ``_get_merged_packs``. The sibling\n        integration catalog reader already guards both the root object and\n        the nested mapping (see ``integrations/catalog.py``); the preset\n        catalog must stay consistent so a malformed payload surfaces as\n        the user-facing ``Invalid preset catalog format`` error instead of\n        a raw Python traceback.\n\n        Args:\n            catalog_data: Parsed JSON payload from the catalog source.\n            url: Source URL — used in the error message so the user can\n                tell which catalog in a multi-catalog stack is malformed.\n\n        Raises:\n            PresetError: If the payload's shape is invalid.\n        \"\"\"\n        if not isinstance(catalog_data, dict):\n            raise PresetError(\n                f\"Invalid preset catalog format from {url}: \"\n                \"expected a JSON object\"\n            )\n        if (\n            \"schema_version\" not in catalog_data\n            or \"presets\" not in catalog_data\n        ):\n            raise PresetError(f\"Invalid preset catalog format from {url}\")\n        if not isinstance(catalog_data.get(\"presets\"), dict):\n            raise PresetError(\n                f\"Invalid preset catalog format from {url}: \"\n                \"'presets' must be a JSON object\"\n            )\n\n    def _load_catalog_config(self, config_path: Path) -> Optional[List[PresetCatalogEntry]]:\n        \"\"\"Load catalog stack configuration from a YAML file.\n\n        Args:","sourceCodeStart":4227,"sourceCodeEnd":4263,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/presets/__init__.py#L4227-L4263","documentation":"The fetched catalog payload parsed as JSON but was not a JSON object at the top level (e.g. a list or a bare string/number). The catalog schema requires a top-level object with schema_version and presets keys; the error names the source URL so you can tell which catalog in a multi-catalog stack is malformed.","triggerScenarios":"A catalog URL returning a JSON array of presets, or a proxy/gateway returning a JSON-encoded error string. Raised in the shape-validation step after json parsing, before any preset entry is read.","commonSituations":"Pointing the catalog URL at a raw GitHub API listing (returns an array), an index of JSON files rather than the catalog document itself, or a CDN serving a wrapped payload.","solutions":["Point the catalog URL at a catalog document whose top level is an object: {\"schema_version\": ..., \"presets\": {...}}.","Verify with: `curl -s <url> | python -m json.tool | head` — the first token must be `{`.","If a proxy rewrites responses, bypass or fix it for this URL."],"exampleFix":"# before (served catalog.json)\n[ {\"id\": \"my-preset\", ...} ]\n\n# after\n{\n  \"schema_version\": 1,\n  \"presets\": { \"my-preset\": { ... } }\n}","handlingStrategy":"type-guard","validationCode":"import json, urllib.request\ndata = json.load(urllib.request.urlopen(url))\nassert isinstance(data, dict), f\"catalog {url} is not a JSON object\"","typeGuard":"def is_catalog_object(payload) -> bool:\n    return isinstance(payload, dict)","tryCatchPattern":"try:\n    manager.refresh_catalogs()\nexcept PresetError as e:\n    if \"expected a JSON object\" in str(e):\n        # the URL serves an array/string; point it at a proper catalog document\n        ...","preventionTips":["Serve {\"schema_version\": ..., \"presets\": {...}} as the whole response.","Do not point catalog URLs at directory listings or API endpoints returning arrays.","Smoke-test catalog URLs with curl | python -m json.tool before shipping config."],"tags":["preset","catalog","json","validation"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}