github/spec-kit · error · BundlerError

Refusing to download {label}: URL is malformed: {url}

Error message

Refusing to download {label}: URL is malformed: {url}

What it means

Spec Kit refuses to download a bundle artifact when urllib cannot parse the URL. _require_https() parses the URL and accesses .hostname and .port; a malformed authority such as an unclosed IPv6 bracket or an invalid port raises ValueError, which is converted to BundlerError. The same guard is applied to the catalog URL, a resolved GitHub API URL, every redirect hop, and the final response URL.

Source

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

        expected_sha256=getattr(resolved.entry, "sha256", None),
    )
    _validate_catalog_manifest(resolved.entry, manifest)
    return manifest


def _require_https(label: str, url: str) -> None:
    from urllib.parse import urlparse

    # urlparse / hostname access raise ValueError on a malformed authority;
    # keep the documented BundlerError contract (older Pythons surface this via
    # the .hostname access below rather than at the urlparse call).
    try:
        parsed = urlparse(url)
        hostname = parsed.hostname
        # Accessing ``port`` performs urllib's syntax/range validation.
        _ = parsed.port
    except ValueError:
        raise BundlerError(
            f"Refusing to download {label}: URL is malformed: {url}"
        ) from None
    is_localhost = hostname in ("localhost", "127.0.0.1", "::1")
    if parsed.scheme != "https" and not (parsed.scheme == "http" and is_localhost):
        raise BundlerError(
            f"Refusing to download {label} over non-HTTPS URL: {url}"
        )
    if not parsed.hostname:
        raise BundlerError(f"Refusing to download {label} from URL with no host: {url}")


def _download_remote_manifest(
    entry_id: str,
    url: str,
    *,
    expected_sha256: str | None = None,
):
    """Fetch a remote bundle artifact over HTTPS and extract its manifest."""

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Copy the URL shown in the error and inspect the active bundle catalog entry that supplied it.
  2. Correct the URL syntax: close IPv6 brackets as `https://[::1]/bundle.zip`, use a numeric port in 0-65535, and include a host.
  3. If the bad URL is a redirect target rather than the catalog URL, fix the release server or redirect configuration and retry.
  4. Re-run `specify bundle info <id>` to confirm the catalog entry now resolves.

Example fix

# catalog.json (before)
"download_url": "https://[::1:8443/bundle.zip"

# catalog.json (after)
"download_url": "https://[::1]:8443/bundle.zip"
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlparse

def is_parseable_download_url(url: str) -> bool:
    try:
        parsed = urlparse(url)
        host = parsed.hostname
        _ = parsed.port
    except ValueError:
        return False
    return bool(host)

Try / catch

from specify_cli.bundler import BundlerError

try:
    specify_bundle_install(bundle_id)
except BundlerError as exc:
    if "URL is malformed" in str(exc):
        show_catalog_url_error(exc)
    else:
        raise

Prevention

When it happens

Trigger: Running `specify bundle info|install|update <id>` when the catalog entry's download_url is malformed, or when the server redirects to a malformed Location URL. Validation runs even with --offline because _download_manifest checks the URL before the offline gate.

Common situations: A hand-edited custom bundle catalog contains `https://[::1/bundle.zip`, `https://host:not-a-port/bundle.zip`, or an out-of-range port. A proxy or release server emits a malformed redirect target.

Understand the failure class

Related errors


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