github/spec-kit · error · BundlerError

Catalog entry ' ' has a non-HTTP(S) download_url ( )…

Error message

Catalog entry '{resolved.entry.id}' has a non-HTTP(S) download_url ({url}); catalog download URLs must be HTTPS (http for localhost) — a file:// URL, a local filesystem path, or a scheme-less value (e.g. 'example.com/bundle.zip') is not accepted. To install a bundle from disk, pass the path directly: 'specify bundle install <path-to-bundle.yml | bundle-dir | .zip>'.

What it means

The download_url scheme check in _download_manifest: catalog URLs must be HTTPS (plain http only for localhost). This fires for scheme-less values ('example.com/bundle.zip'), file:// URLs, bare filesystem paths, and Windows drive paths ('C:\bundle.yml' — urlparse sees a single-letter scheme), each rejected with a redirect telling the user how to install from disk instead. The check runs before the offline gate so the real problem is reported in every mode.

Solutions

  1. Host the artifact over HTTPS and set download_url to the https:// URL.
  2. To install from local disk, do not fix the catalog — pass the path positionally: specify bundle install <path-to-bundle.yml | bundle-dir | .zip>.
  3. For local HTTP testing, use http://localhost/... which the localhost exception permits.

Example fix

# before (catalog entry)
"download_url": "file:///srv/bundles/my-bundle.zip"

# after
"download_url": "https://bundles.example.com/my-bundle.zip"
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlparse
import re

url = resolved.entry.download_url
parsed = urlparse(url)
scheme = parsed.scheme.lower()
if scheme in ("", "file") or re.match(r"^[A-Za-z]:[\\/]", url):
    raise SystemExit("Catalog download_url must be https:// (http for localhost); use a path argument for disk installs")

Type guard

def is_https_catalog_url(url: str) -> bool:
    from urllib.parse import urlparse
    import re
    if re.match(r"^[A-Za-z]:[\\/]", url):
        return False
    scheme = urlparse(url).scheme.lower()
    return scheme == "https" or (scheme == "http" and "localhost" in urlparse(url).netloc)

Try / catch

try:
    manifest = _download_manifest(resolved, offline=offline)
except BundlerError as exc:
    if "non-HTTP(S) download_url" in str(exc):
        # host the artifact over HTTPS, or install locally via positional path
        ...

Prevention

When it happens

Trigger: A catalog entry whose download_url is 'file:///bundles/my-bundle.zip', '/home/me/bundle.zip', 'C:\bundles\bundle.zip', or 'example.com/bundle.zip'; any 'specify bundle install <catalog-id>' resolving to that entry.

Common situations: Private/internal catalogs written with LAN file shares; Windows-path entries; users copying a path into the catalog instead of hosting the artifact over HTTPS.

Related errors


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

Appendix: source

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

    # 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
    # every catalog system; installing from disk is done by passing the path
    # positionally, which never reaches URL resolution. Give an actionable
    # error rather than accepting a scheme the rest of the codebase rejects.
    if scheme in ("", "file") or re.match(r"^[A-Za-z]:[\\/]", url):
        raise BundlerError(
            f"Catalog entry '{resolved.entry.id}' has a non-HTTP(S) download_url "
            f"({url}); catalog download URLs must be HTTPS (http for localhost) — "
            "a file:// URL, a local filesystem path, or a scheme-less value "
            "(e.g. 'example.com/bundle.zip') is not accepted. "
            "To install a bundle from disk, pass the path directly: "
            "'specify bundle install <path-to-bundle.yml | bundle-dir | .zip>'."
        )

    # Validate the scheme/host *before* the offline gate so an invalid or
    # non-HTTPS download_url reports the real problem in every mode, rather
    # than a misleading "Network access disabled" under --offline.
    # (_download_remote_manifest re-checks this, but only once network access
    # is permitted.) HTTPS-only, http allowed for localhost.
    _require_https(f"bundle '{resolved.entry.id}'", url)

    if offline:
        raise BundlerError(
            f"Network access disabled; cannot download bundle '{resolved.entry.id}' "

View on GitHub (pinned to bf88c9f9a8)