{"record":{"id":"b5720f2d5a625cfc","repo":"github/spec-kit","slug":"invalid-preset-catalog-format-from-url-presets","errorCode":null,"errorMessage":"Invalid preset catalog format from {url}: 'presets' must be a JSON object","messagePattern":"Invalid preset catalog format from (.+?): 'presets' must be a JSON object","errorType":"exception","errorClass":"PresetError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/presets/__init__.py","lineNumber":4255,"sourceCode":"            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:\n            config_path: Path to preset-catalogs.yml\n\n        Returns:\n            Ordered list of PresetCatalogEntry objects, or None if file\n            doesn't exist or contains no valid catalog entries.\n\n        Raises:\n            PresetValidationError: If any catalog entry has an invalid URL,\n                the file cannot be parsed, or a priority value is invalid.\n        \"\"\"","sourceCodeStart":4237,"sourceCodeEnd":4273,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/presets/__init__.py#L4237-L4273","documentation":"The catalog's top-level `presets` key exists but is not a JSON object — the schema requires presets to be a mapping keyed by preset id, not an array. This is a distinct, later check from the missing-keys case so the author knows exactly what shape to fix.","triggerScenarios":"A catalog document with `\"presets\": [ ... ]` (a JSON array of preset entries), or presets as a string/number. schema_version is present and correct, so only the presets shape is wrong.","commonSituations":"Authoring a catalog as a list because that feels natural, or converting from a format where presets were array-valued.","solutions":["Convert presets to an object keyed by preset id: {\"presets\": {\"my-preset\": {...}}} instead of an array.","If publishing via a script, dump a dict (yaml/json safe_dump of a mapping), not a list."],"exampleFix":"# before\n{\n  \"schema_version\": 1,\n  \"presets\": [ {\"id\": \"my-preset\", \"name\": \"My Preset\"} ]\n}\n\n# after\n{\n  \"schema_version\": 1,\n  \"presets\": {\n    \"my-preset\": {\"name\": \"My Preset\", \"description\": \"...\"}\n  }\n}","handlingStrategy":"type-guard","validationCode":"data = json.load(urllib.request.urlopen(url))\nassert isinstance(data.get(\"presets\"), dict), \"catalog 'presets' must be an object keyed by preset id\"","typeGuard":"def presets_is_mapping(payload: dict) -> bool:\n    return isinstance(payload.get(\"presets\"), dict)","tryCatchPattern":"try:\n    manager.refresh_catalogs()\nexcept PresetError as e:\n    if \"'presets' must be a JSON object\" in str(e):\n        # convert the array to an id-keyed object and republish the catalog\n        ...","preventionTips":["\"presets\" is a map of preset id -> entry, never an array.","When generating catalogs from scripts, serialize a dict for presets.","Check the whole shape (object root, schema_version, presets object) in one CI validation step."],"tags":["preset","catalog","json","schema"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}