github/spec-kit · error · ExtensionError

Invalid JSON in catalog from {entry.url}: {e}

Error message

Invalid JSON in catalog from {entry.url}: {e}

What it means

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.

Source

Thrown at src/specify_cli/extensions/__init__.py:3926

                cache_meta_file.write_text(
                    json.dumps(
                        {
                            "cached_at": datetime.now(timezone.utc).isoformat(),
                            "catalog_url": entry.url,
                        },
                        indent=2,
                    ),
                    encoding="utf-8",
                )
            except OSError:
                pass  # Cache is best-effort; proceed with fetched data

            return catalog_data

        except urllib.error.URLError as e:
            raise ExtensionError(f"Failed to fetch catalog from {entry.url}: {e}")
        except json.JSONDecodeError as e:
            raise ExtensionError(f"Invalid JSON in catalog from {entry.url}: {e}")

    def _get_merged_extensions(
        self, force_refresh: bool = False
    ) -> List[Dict[str, Any]]:
        """Fetch and merge extensions from all active catalogs.

        Higher-priority (lower priority number) catalogs win on conflicts
        (same extension id in two catalogs). Each extension dict is annotated with:
          - _catalog_name: name of the source catalog
          - _install_allowed: whether installation is allowed from this catalog

        Catalogs that fail to fetch are skipped. Raises ExtensionError only if
        ALL catalogs fail.

        Args:
            force_refresh: If True, bypass all caches

        Returns:

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Fetch and inspect the raw body: `curl -s <url> | head -c 400` — look for HTML or error text
  2. Point the catalog entry at the raw JSON endpoint (e.g. raw.githubusercontent.com, not the blob page)
  3. Fix authentication so the catalog server returns JSON instead of a login redirect
  4. Re-serialize hand-edited JSON: `python -m json.tool catalog.json` to find the syntax error

Example fix

# before: catalog URL pointing at the GitHub web UI
url: https://github.com/org/repo/blob/main/catalog.json

# after: raw file endpoint
url: https://raw.githubusercontent.com/org/repo/main/catalog.json
Defensive patterns

Strategy: validation

Validate before calling

import json, urllib.request

def fetch_valid_catalog_json(url: str) -> dict:
    with urllib.request.urlopen(url, timeout=10) as r:
        body = r.read()
    return json.loads(body)  # raises JSONDecodeError early, with body inspectable

Try / catch

from specify_cli.extensions import ExtensionError

try:
    ...
except ExtensionError as e:
    if 'Invalid JSON in catalog' in str(e):
        # endpoint served HTML/empty; check auth and use the raw file URL

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14). Data as JSON: /api/errors/2a863bffe531aa9c. Report an issue: GitHub.