{"record":{"id":"2a863bffe531aa9c","repo":"github/spec-kit","slug":"invalid-json-in-catalog-from-entry-url-e","errorCode":null,"errorMessage":"Invalid JSON in catalog from {entry.url}: {e}","messagePattern":"Invalid JSON in catalog from (.+?): (.+?)","errorType":"exception","errorClass":"ExtensionError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/extensions/__init__.py","lineNumber":3926,"sourceCode":"                cache_meta_file.write_text(\n                    json.dumps(\n                        {\n                            \"cached_at\": datetime.now(timezone.utc).isoformat(),\n                            \"catalog_url\": entry.url,\n                        },\n                        indent=2,\n                    ),\n                    encoding=\"utf-8\",\n                )\n            except OSError:\n                pass  # Cache is best-effort; proceed with fetched data\n\n            return catalog_data\n\n        except urllib.error.URLError as e:\n            raise ExtensionError(f\"Failed to fetch catalog from {entry.url}: {e}\")\n        except json.JSONDecodeError as e:\n            raise ExtensionError(f\"Invalid JSON in catalog from {entry.url}: {e}\")\n\n    def _get_merged_extensions(\n        self, force_refresh: bool = False\n    ) -> List[Dict[str, Any]]:\n        \"\"\"Fetch and merge extensions from all active catalogs.\n\n        Higher-priority (lower priority number) catalogs win on conflicts\n        (same extension id in two catalogs). Each extension dict is annotated with:\n          - _catalog_name: name of the source catalog\n          - _install_allowed: whether installation is allowed from this catalog\n\n        Catalogs that fail to fetch are skipped. Raises ExtensionError only if\n        ALL catalogs fail.\n\n        Args:\n            force_refresh: If True, bypass all caches\n\n        Returns:","sourceCodeStart":3908,"sourceCodeEnd":3944,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/extensions/__init__.py#L3908-L3944","documentation":"Raised when a catalog URL responds successfully but the body is not valid JSON, so json parsing raises JSONDecodeError. The message embeds the decode error and the source URL, distinguishing 'reachable but malformed' from the network-failure variant.","triggerScenarios":"Catalog URL returns HTML (error page, login page, SPA), an empty body, truncated JSON, or a BOM-prefixed document; json.loads fails inside the fetch for entry.url.","commonSituations":"Catalog URL behind auth redirecting to an HTML login page; reverse proxy returning a 200 HTML maintenance page; URL pointing at a human-readable web view of the JSON file rather than the raw file; encoding issues from hand-edited files.","solutions":["Fetch and inspect the raw body: `curl -s <url> | head -c 400` — look for HTML or error text","Point the catalog entry at the raw JSON endpoint (e.g. raw.githubusercontent.com, not the blob page)","Fix authentication so the catalog server returns JSON instead of a login redirect","Re-serialize hand-edited JSON: `python -m json.tool catalog.json` to find the syntax error"],"exampleFix":"# before: catalog URL pointing at the GitHub web UI\nurl: https://github.com/org/repo/blob/main/catalog.json\n\n# after: raw file endpoint\nurl: https://raw.githubusercontent.com/org/repo/main/catalog.json","handlingStrategy":"validation","validationCode":"import json, urllib.request\n\ndef fetch_valid_catalog_json(url: str) -> dict:\n    with urllib.request.urlopen(url, timeout=10) as r:\n        body = r.read()\n    return json.loads(body)  # raises JSONDecodeError early, with body inspectable","typeGuard":null,"tryCatchPattern":"from specify_cli.extensions import ExtensionError\n\ntry:\n    ...\nexcept ExtensionError as e:\n    if 'Invalid JSON in catalog' in str(e):\n        # endpoint served HTML/empty; check auth and use the raw file URL","preventionTips":["Point catalog config at raw JSON endpoints, not UI or auth-wrapped pages","json.tool-validate any hand-edited catalog before upload"],"tags":["extensions","catalog","json","network"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}