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
- Fetch the URL manually (`curl -s <catalog-url> | python -m json.tool`) and confirm the top level is `{...}`
- Wrap array data into the catalog shape: {"schema_version": 1, "extensions": {"id": {...}}}
- If the URL is wrong, correct it in .specify/extension-catalogs.yml / ~/.specify/extension-catalogs.yml / SPECKIT_CATALOG_URL
- 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
- Validate catalog JSON shape (object + schema_version + extensions) in CI before publishing
- Prefer raw-file endpoints over API endpoints whose top level may be an array
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
- Invalid catalog format from {url}: 'extensions' must be a JS
- Invalid catalog format from {url}
- Invalid JSON in catalog from {entry.url}: {e}
- Invalid JSON in catalog: {e}
- Invalid catalog format from {entry.url}: {shape_error}
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/e584ca42869876e2.
Report an issue: GitHub.