github/spec-kit · error · BundlerError

A catalog source is missing its 'id'.

Error message

A catalog source is missing its 'id'.

What it means

Raised by CatalogSource.from_dict() when a source mapping has no 'id' key or an id that is empty/whitespace after stripping. The id is the primary handle for overriding built-ins and deduplicating sources, so it is mandatory for every configured source.

Source

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

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}."
            ) from None
        return cls(
            id=source_id,
            url=url,

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Add a non-empty 'id' string to the source mapping.
  2. If creating sources programmatically, prefer add_source() which derives the id from the url when omitted.
  3. Check for misspelled keys like 'source_id' or 'name' where 'id' was intended.

Example fix

# before
{"url": "https://example.com/c.json", "priority": 5, "install_policy": "install-allowed"}

# after
{"id": "example", "url": "https://example.com/c.json", "priority": 5, "install_policy": "install-allowed"}
Defensive patterns

Strategy: validation

Validate before calling

def validate_source_entry(entry: dict) -> None:
    if not str(entry.get("id", "")).strip():
        raise ValueError("source entry missing non-empty 'id'")
    CatalogSource.from_dict(entry, Scope.PROJECT)

Type guard

def has_source_id(entry: dict) -> bool:
    return isinstance(entry, dict) and bool(str(entry.get("id", "")).strip())

Try / catch

try:
    CatalogSource.from_dict(entry, Scope.PROJECT)
except BundlerError as e:
    if "missing its 'id'" in str(e):
        entry["id"] = derive_id(entry["url"])  # repair then retry

Prevention

When it happens

Trigger: A config entry like {"url": ..., "priority": ...} with no id; an id of "" or " "; a YAML entry where 'id' was accidentally nested under another key or misspelled (e.g. 'name').

Common situations: Hand-written config entries that assume the id is derived from the url (only add_source() does derivation, not from_dict); key typos; copy-paste between different config formats.

Related errors


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