github/spec-kit · error · BundlerError

Each catalog source must be a mapping.

Error message

Each catalog source must be a mapping.

What it means

Raised by CatalogSource.from_dict() when a single catalog source entry in configuration is not a JSON/YAML object (dict). Sources are loaded from project and user config files whose top-level shape must be a list of mappings; any scalar, list, or null element fails this guard before field validation begins.

Source

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

)


@dataclass(frozen=True)
class CatalogSource:
    id: str
    url: str
    priority: int
    install_policy: InstallPolicy
    scope: Scope = Scope.PROJECT

    @property
    def install_allowed(self) -> bool:
        return self.install_policy is InstallPolicy.INSTALL_ALLOWED

    @classmethod
    def from_dict(cls, data: Any, scope: Scope) -> "CatalogSource":
        if not isinstance(data, dict):
            raise BundlerError("Each catalog source must be a mapping.")
        source_id = str(data.get("id", "")).strip()
        url = str(data.get("url", "")).strip()
        if not source_id:
            raise BundlerError("A catalog source is missing its 'id'.")
        if not url:
            raise BundlerError(f"Catalog source '{source_id}' is missing its 'url'.")
        priority = data.get("priority")
        if priority is None:
            raise BundlerError(f"Catalog source '{source_id}' is missing its 'priority'.")
        if isinstance(priority, bool) or not isinstance(priority, (int, str)):
            raise BundlerError(
                f"Catalog source '{source_id}' has a non-integer priority: {priority!r}."
            )
        try:
            priority_int = int(priority)
        except (TypeError, ValueError):
            raise BundlerError(
                f"Catalog source '{source_id}' has a non-integer priority: {priority!r}."

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Make each element of the sources list an object with at least id, url, priority, and install_policy keys.
  2. Validate the config with a JSON linter or json.load plus an isinstance(item, dict) check before relying on it.
  3. Regenerate the config through add_source() instead of editing the file by hand.

Example fix

# before (config file)
[{"id": "a", "url": "https://x/c.json", "priority": 5, "install_policy": "install-allowed"}, "https://y/c.json"]

# after
[{"id": "a", "url": "https://x/c.json", "priority": 5, "install_policy": "install-allowed"},
 {"id": "b", "url": "https://y/c.json", "priority": 5, "install_policy": "install-allowed"}]
Defensive patterns

Strategy: type-guard

Validate before calling

import json

data = json.loads(config_text)
assert isinstance(data, list), "catalog config must be a list"
for item in data:
    if not isinstance(item, dict):
        raise ValueError(f"catalog source entry is not an object: {item!r}")

Type guard

def is_source_mapping(item: object) -> bool:
    return isinstance(item, dict)

Try / catch

try:
    CatalogSource.from_dict(item, Scope.PROJECT)
except BundlerError as e:
    log_config_error(config_path, e)  # point the user at the offending entry

Prevention

When it happens

Trigger: A catalog config file whose 'sources'-style list contains a bare string (e.g. a url) instead of an object; a YAML dash-item that parsed as a scalar; a null entry from a trailing comma or malformed JSON merge.

Common situations: Hand-editing the catalog config and writing just a url per line; JSON produced by string concatenation instead of a serializer; config templating that renders an empty placeholder value.

Related errors


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