github/spec-kit · error · ExtensionError

Invalid catalog format from {url}: expected a JSON object

Error message

Invalid catalog format from {url}: expected a JSON object

What it means

Catalog payload validation (_validate_catalog_data) rejects a fetched catalog whose parsed JSON is not an object — e.g. a JSON array, string, or number. The catalog format requires a top-level object with schema_version and extensions, so any other top-level type fails immediately with the 'expected a JSON object' variant.

Source

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

        ``{"extensions": []}`` or ``{"extensions": null}`` slip through
        here and then crash with ``AttributeError: 'list' object has no
        attribute 'items'`` deep inside ``_get_merged_extensions``. The
        sibling integration catalog reader already guards both the root
        object and the nested mapping (see ``integrations/catalog.py``);
        the extension catalog must stay consistent so a malformed payload
        surfaces as the user-facing ``Invalid catalog format`` error
        instead of a raw Python traceback.

        Args:
            catalog_data: Parsed JSON payload from the catalog source.
            url: Source URL — used in the error message so the user can
                tell which catalog in a multi-catalog stack is malformed.

        Raises:
            ExtensionError: If the payload's shape is invalid.
        """
        if not isinstance(catalog_data, dict):
            raise ExtensionError(
                f"Invalid catalog format from {url}: expected a JSON object"
            )
        if "schema_version" not in catalog_data or "extensions" not in catalog_data:
            raise ExtensionError(f"Invalid catalog format from {url}")
        if not isinstance(catalog_data.get("extensions"), dict):
            raise ExtensionError(
                f"Invalid catalog format from {url}: 'extensions' must be a JSON object"
            )

    def get_active_catalogs(self) -> List[CatalogEntry]:
        """Get the ordered list of active catalogs.

        Resolution order:
        1. SPECKIT_CATALOG_URL env var — single catalog replacing all defaults
        2. Project-level .specify/extension-catalogs.yml
        3. User-level ~/.specify/extension-catalogs.yml
        4. Built-in default stack (default + community)

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Fetch the URL manually (`curl -s <catalog-url> | python -m json.tool`) and confirm the top level is `{...}`
  2. Wrap array data into the catalog shape: {"schema_version": 1, "extensions": {"id": {...}}}
  3. If the URL is wrong, correct it in .specify/extension-catalogs.yml / ~/.specify/extension-catalogs.yml / SPECKIT_CATALOG_URL
  4. Fall back to the default catalog stack by removing the custom catalog entry

Example fix

# before: catalog URL returns a bare array
[ {"id": "my-ext", "name": "My Ext"} ]

# after: proper catalog object
{
  "schema_version": 1,
  "extensions": {
    "my-ext": {"name": "My Ext", "description": "..."}
  }
}
Defensive patterns

Strategy: validation

Validate before calling

import json, urllib.request

def fetch_catalog_object(url: str) -> dict:
    with urllib.request.urlopen(url, timeout=10) as r:
        data = json.load(r)
    if not isinstance(data, dict):
        raise ValueError(f'{url}: top-level JSON must be an object, got {type(data).__name__}')
    return data

Type guard

def is_catalog_shaped(data: object) -> bool:
    return (
        isinstance(data, dict)
        and 'schema_version' in data
        and 'extensions' in data
        and isinstance(data['extensions'], dict)
    )

Try / catch

from specify_cli.extensions import ExtensionError

try:
    ...catalog fetch...
except ExtensionError as e:
    if 'expected a JSON object' in str(e):
        # URL returns non-object JSON; fix endpoint or wrap array data

Prevention

When it happens

Trigger: A catalog URL (from SPECKIT_CATALOG_URL, .specify/extension-catalogs.yml, or the user-level catalog config) returning valid JSON that is not an object, e.g. `[...]` or `"ok"` — such as an API returning a bare list of extensions.

Common situations: Pointing the catalog URL at a JSON array endpoint instead of the catalog document; a proxy or CDN serving a JSON status string; hand-written catalog files with the braces omitted.

Related errors


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