github/spec-kit · error · BundlerError

Catalog source '{source_id}' is missing its 'url'.

Error message

Catalog source '{source_id}' is missing its 'url'.

What it means

Raised by CatalogSource.from_dict() when a source mapping has an 'id' but no 'url' (or a blank one). Every catalog source must point at a fetchable location — an https url, an http url for localhost, or a local file path — because the stack resolves bundles by fetching that url.

Source

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

    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,
            priority=priority_int,
            install_policy=InstallPolicy.parse(data.get("install_policy")),

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Add the 'url' key with the catalog's https url or a local file path.
  2. Verify the key is exactly 'url' and the value is non-blank.
  3. Regenerate the entry via add_source(root, '<id>', '<url>', ...).

Example fix

# before
{"id": "example", "priority": 5, "install_policy": "install-allowed"}

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

Strategy: validation

Validate before calling

if not str(entry.get("url", "")).strip():
    raise ValueError("source entry missing non-empty 'url'")
CatalogSource.from_dict(entry, Scope.PROJECT)

Type guard

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

Try / catch

try:
    CatalogSource.from_dict(entry, Scope.PROJECT)
except BundlerError as e:
    if "missing its 'url'" in str(e):
        flag_entry_for_human_review(entry)

Prevention

When it happens

Trigger: A config entry with id/priority/policy but no url; url key misspelled ('link', 'href', 'src'); url value of "" or whitespace; hand-edited file where the url line was deleted.

Common situations: Partial hand-edits; templating that drops the url when a variable is empty; converting configs between tools that use different key names.

Related errors


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