github/spec-kit · error · PresetError
Invalid preset catalog format from {url}: expected a JSON ob
Error message
Invalid preset catalog format from {url}: expected a JSON object What it means
The fetched catalog payload parsed as JSON but was not a JSON object at the top level (e.g. a list or a bare string/number). The catalog schema requires a top-level object with schema_version and presets keys; the error names the source URL so you can tell which catalog in a multi-catalog stack is malformed.
Source
Thrown at src/specify_cli/presets/__init__.py:4245
``{"presets": []}`` or ``{"presets": null}`` slip through here and
then crash with ``AttributeError: 'list' object has no attribute
'items'`` deep inside ``_get_merged_packs``. The sibling
integration catalog reader already guards both the root object and
the nested mapping (see ``integrations/catalog.py``); the preset
catalog must stay consistent so a malformed payload surfaces as
the user-facing ``Invalid preset 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:
PresetError: If the payload's shape is invalid.
"""
if not isinstance(catalog_data, dict):
raise PresetError(
f"Invalid preset catalog format from {url}: "
"expected a JSON object"
)
if (
"schema_version" not in catalog_data
or "presets" not in catalog_data
):
raise PresetError(f"Invalid preset catalog format from {url}")
if not isinstance(catalog_data.get("presets"), dict):
raise PresetError(
f"Invalid preset catalog format from {url}: "
"'presets' must be a JSON object"
)
def _load_catalog_config(self, config_path: Path) -> Optional[List[PresetCatalogEntry]]:
"""Load catalog stack configuration from a YAML file.
Args:View on GitHub (pinned to bf88c9f9a8)
Solutions
- Point the catalog URL at a catalog document whose top level is an object: {"schema_version": ..., "presets": {...}}.
- Verify with: `curl -s <url> | python -m json.tool | head` — the first token must be `{`.
- If a proxy rewrites responses, bypass or fix it for this URL.
Example fix
# before (served catalog.json)
[ {"id": "my-preset", ...} ]
# after
{
"schema_version": 1,
"presets": { "my-preset": { ... } }
} Defensive patterns
Strategy: type-guard
Validate before calling
import json, urllib.request
data = json.load(urllib.request.urlopen(url))
assert isinstance(data, dict), f"catalog {url} is not a JSON object" Type guard
def is_catalog_object(payload) -> bool:
return isinstance(payload, dict) Try / catch
try:
manager.refresh_catalogs()
except PresetError as e:
if "expected a JSON object" in str(e):
# the URL serves an array/string; point it at a proper catalog document
... Prevention
- Serve {"schema_version": ..., "presets": {...}} as the whole response.
- Do not point catalog URLs at directory listings or API endpoints returning arrays.
- Smoke-test catalog URLs with curl | python -m json.tool before shipping config.
When it happens
Trigger: A catalog URL returning a JSON array of presets, or a proxy/gateway returning a JSON-encoded error string. Raised in the shape-validation step after json parsing, before any preset entry is read.
Common situations: Pointing the catalog URL at a raw GitHub API listing (returns an array), an index of JSON files rather than the catalog document itself, or a CDN serving a wrapped payload.
Related errors
- Invalid catalog format from {url}: expected a JSON object
- Invalid catalog format from {url}: 'extensions' must be a JS
- Invalid catalog format from {entry.url}: {shape_error}
- Catalog URL is malformed: {url}
- Catalog URL must be a valid URL with a host.
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/a89f6b579cfc6427.
Report an issue: GitHub.