github/spec-kit · error · BundlerError
Malformed catalog config at {path}: expected a mapping at th
Error message
Malformed catalog config at {path}: expected a mapping at the top level, got {type(data).__name__}. What it means
Raised by the reader of `.specify/bundle-catalogs.yml` when the parsed YAML top level is not a mapping. The file shape is `{schema_version, catalogs: [...]}`; a top-level list, scalar, or explicit null is treated as corrupt, consistently with the other reader (`models/catalog._merge_config`). The message names the actual type received.
Source
Thrown at src/specify_cli/bundler/commands_impl/catalog_config.py:49
def _config_path(project_root: Path) -> Path:
return Path(project_root) / ".specify" / CONFIG_FILENAME
def _read(project_root: Path) -> list[dict]:
# Confine the read (parity with the write path's within= guard): refuse to
# follow a symlinked or traversal-escaping .specify that resolves outside
# project_root.
path = ensure_within(project_root, _config_path(project_root))
if not path.exists():
return []
# ``load_yaml`` returns ``{}`` only for an empty document and the raw parse
# otherwise, so a non-mapping top level — a falsy ``[]``/``false``/``0``/``''``
# or an explicit null (``load_yaml`` -> ``None``) — is caught by the isinstance
# guard below and raised like a truthy one, staying consistent with the other
# reader of this file (models/catalog._merge_config).
data = load_yaml(path)
if not isinstance(data, dict):
raise BundlerError(
f"Malformed catalog config at {path}: expected a mapping at the top "
f"level, got {type(data).__name__}."
)
schema_version = data.get("schema_version")
if schema_version is not None and (
str(schema_version).strip().split(".")[0]
!= CONFIG_SCHEMA_VERSION.split(".")[0]
):
raise BundlerError(
f"Unsupported catalog config schema version "
f"'{str(schema_version).strip()}' at {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):View on GitHub (pinned to bf88c9f9a8)
Solutions
- Wrap the contents in a top-level mapping with `schema_version` and `catalogs` keys
- If the file is hopelessly mangled, delete it — built-in defaults still work and it will be recreated
- Prefer `specify bundle catalog add ...` (add_source) over hand-editing
Example fix
# before (root is a list)
- id: community
url: https://example.com/c.json
# after
schema_version: "1.0"
catalogs:
- id: community
url: https://example.com/c.json Defensive patterns
Strategy: try-catch
Validate before calling
import yaml
def looks_like_catalog_config(path) -> bool:
try:
with open(path) as f:
data = yaml.safe_load(f)
except yaml.YAMLError:
return False
return data is None or isinstance(data, dict) Type guard
def is_catalog_config_mapping(data: object) -> bool:
return data is None or isinstance(data, dict) Try / catch
from specify_cli.bundler import BundlerError
try:
catalogs = read_catalog_config(project_root)
except BundlerError as exc:
if "expected a mapping at the top level" in str(exc):
# back up the corrupt file, regenerate from `specify bundle catalog add`
raise
raise Prevention
- Never hand-edit bundle-catalogs.yml — use the catalog add/remove commands
- yaml.safe_load + isinstance(dict, ...) check in any script that pre-processes the file
When it happens
Trigger: `bundle-catalogs.yml` whose root node is a list (e.g. a bare `- id: ...` sequence), a scalar/string, or a `null` document — `load_yaml` returns `{}` only for an empty document, so anything else non-dict lands here.
Common situations: Hand-editing the file and dropping the outer mapping; pasting a catalog entry list directly; a truncated file after an interrupted write; converting from another YAML format that is a list at the root.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Malformed catalog config at {config_path}: expected a mappin
- Malformed catalog config at {config_path}: 'catalogs' must b
- Malformed catalog config at {path}: 'catalogs' must be a lis
- Malformed catalog config at {path}: each catalog entry must
- A catalog url is required.
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/1b61c2dc432c56b1.
Report an issue: GitHub.