github/spec-kit · error · BundlerError

Malformed catalog config at {config_path}: 'catalogs' must b

Error message

Malformed catalog config at {config_path}: 'catalogs' must be a list, got {type(catalogs).__name__}.

What it means

Raised when a bundle-catalogs.yml has a top-level mapping but the 'catalogs' key holds a non-list value (e.g. 'catalogs: 5', 'catalogs: false', 'catalogs: {}', or a string). Only an absent or null 'catalogs' is treated as 'nothing to merge'; every other non-list is malformed. Without this guard, a truthy scalar would crash later with a raw TypeError while falsy non-lists would be silently swallowed.

Source

Thrown at src/specify_cli/bundler/models/catalog.py:312

            f"Unsupported catalog config schema version "
            f"'{str(schema_version).strip()}' at {config_path}; this Spec Kit "
            f"understands version {CONFIG_SCHEMA_VERSION}. The file may have been "
            "written by a newer version or is corrupt."
        )
    catalogs = data.get("catalogs")
    if catalogs is None:
        return
    if not isinstance(catalogs, list):
        # Treat only an absent/``None`` ``catalogs`` as "nothing to merge"; any
        # other non-list value (``catalogs: 5``, ``false``, ``0``, ``''``,
        # ``{}``) is a malformed config and must raise, not be silently skipped
        # by a falsy check. Otherwise a truthy scalar would raise a raw
        # ``TypeError: 'int' object is not iterable`` from the loop below, while
        # falsy non-lists would be swallowed. Report the same actionable
        # BundlerError the sibling reader of this file raises
        # (commands_impl/catalog_config.py) so both readers of
        # bundle-catalogs.yml agree. An empty list stays valid (loop is a no-op).
        raise BundlerError(
            f"Malformed catalog config at {config_path}: 'catalogs' must be a "
            f"list, got {type(catalogs).__name__}."
        )
    for raw in catalogs:
        src = CatalogSource.from_dict(raw, scope)
        by_id[src.id] = src

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Change the 'catalogs' value to a YAML list of catalog mappings (each entry starting with '- ').
  2. If you meant no catalogs, remove the key entirely or set it to null — do not use false/0/''/{}.
  3. Leave 'catalogs: []' if you want an explicitly empty list.

Example fix

# before (bundle-catalogs.yml)
catalogs:
  id: my-catalog
  url: https://example.com/catalog.yml

# after
catalogs:
  - id: my-catalog
    url: https://example.com/catalog.yml
Defensive patterns

Strategy: validation

Validate before calling

import yaml
from pathlib import Path

def catalogs_key_is_list(path: Path) -> bool:
    data = yaml.safe_load(path.read_text()) or {}
    catalogs = data.get("catalogs")
    return catalogs is None or isinstance(catalogs, list)

Type guard

def is_optional_list(v: object) -> bool:
    return v is None or isinstance(v, list)

Try / catch

try:
    resolve_catalogs(...)
except BundlerError as e:
    if "'catalogs' must be a list" in str(e):
        # fix the YAML sequence shape, then retry
        ...

Prevention

When it happens

Trigger: A config file with 'catalogs:' followed by a scalar, mapping, or boolean instead of a YAML sequence is read via _merge_config during bundle search/install. An empty list (catalogs: []) is valid and merges nothing.

Common situations: Writing 'catalogs:' with an inline flow mapping '{...}' instead of a list; forgetting the '-' bullet so a single catalog becomes a mapping; setting 'catalogs: false' while experimenting; hand-merging two config files incorrectly.

Understand the failure class

Related errors


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