github/spec-kit · error · BundlerError

Catalog entry '{resolved.entry.id}' has no download_url; can

Error message

Catalog entry '{resolved.entry.id}' has no download_url; cannot resolve its manifest.

What it means

_download_manifest fetches a catalog-resolved bundle's manifest from its entry.download_url; this fires when the catalog entry carries no download_url at all, so there is nothing to fetch. It is a data-quality error in the catalog (or its cached copy), not in the user's project.

Source

Thrown at src/specify_cli/commands/bundle/__init__.py:853

def _download_manifest(resolved, *, offline: bool):
    """Resolve a bundle's manifest from its catalog ``download_url``.

    Catalog ``download_url``s are HTTPS-only (``http`` allowed for localhost),
    matching the extensions/presets/workflows catalog systems. Remote URLs are
    fetched with the shared authenticated, redirect-validated HTTP client, and
    only when not ``--offline``.

    Local and ``file://`` sources are intentionally not resolved here: to
    install a bundle from disk, pass the path positionally
    (``specify bundle install ./path/to/bundle.yml`` — a bundle directory or a
    ``.zip`` artifact also works), which :func:`_local_manifest_source` handles
    before catalog resolution and which never touches ``download_url``.
    """
    from urllib.parse import urlparse

    url = resolved.entry.download_url
    if not url:
        raise BundlerError(
            f"Catalog entry '{resolved.entry.id}' has no download_url; cannot resolve "
            "its manifest."
        )
    # A malformed authority (e.g. an unclosed IPv6 bracket ``https://[::1``)
    # makes urlparse raise ValueError. Surface it as the documented
    # BundlerError, like the sibling ``_validate_remote_url``, rather than
    # leaking a raw ValueError past the callers, which only catch BundlerError.
    try:
        parsed = urlparse(url)
    except ValueError:
        raise BundlerError(
            f"Catalog entry '{resolved.entry.id}' has a malformed download_url: {url}"
        ) from None
    scheme = parsed.scheme.lower()

    # ``file://`` URLs and bare filesystem paths (including Windows drive paths
    # like ``C:\bundle.yml``, which urlparse reads as a single-letter scheme)
    # are not valid catalog download URLs. Catalog URLs are HTTPS-only across

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Install from a concrete artifact instead: specify bundle install <bundle.yml | dir | .zip>.
  2. Fix the catalog entry by adding its download_url and refresh/re-cache the catalog.
  3. Report to the catalog maintainer if it is a third-party index.

Example fix

# before (catalog entry)
{"id": "my-bundle", "name": "My Bundle"}

# after
{"id": "my-bundle", "name": "My Bundle", "download_url": "https://example.com/my-bundle.zip"}
Defensive patterns

Strategy: validation

Validate before calling

resolved = stack.resolve(bundle_id)
if not resolved.entry.download_url:
    raise SystemExit(f"Catalog entry '{resolved.entry.id}' lacks download_url; install from a path instead")

Type guard

def entry_is_downloadable(resolved) -> bool:
    return bool(getattr(resolved.entry, "download_url", None))

Try / catch

try:
    manifest = _download_manifest(resolved, offline=offline)
except BundlerError as exc:
    if "no download_url" in str(exc):
        # install from local artifact; report catalog data issue
        ...

Prevention

When it happens

Trigger: stack.resolve(bundle_id) succeeds and _download_manifest is called, but resolved.entry.download_url is falsy — a registry row published without a download link.

Common situations: Curated catalogs with incomplete entries; hand-edited catalog JSON/YAML missing the field; a bundle indexed for discovery only but accidentally resolvable from an install-allowed source.

Related errors


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