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
- Copy the URL shown in the error and inspect the active bundle catalog entry that supplied it.
- Correct the URL syntax: close IPv6 brackets as `https://[::1]/bundle.zip`, use a numeric port in 0-65535, and include a host.
- If the bad URL is a redirect target rather than the catalog URL, fix the release server or redirect configuration and retry.
- 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
- Run automated catalog validation that parses every download_url with urlparse and checks .hostname/.port.
- Generate URLs with urljoin/urlencode rather than string concatenation.
- Test custom catalogs with `specify bundle search --json` before making them the install source.
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Refusing to download {label} from URL with no host: {url}
- Refusing to download {label} over non-HTTPS URL: {url}
- Invalid catalog url: '{url}'.
- Unsupported catalog url scheme '{parsed.scheme}://' in '{url
- Catalog url must use HTTPS (got {parsed.scheme}://). HTTP is
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/c323807e2c0e9359.
Report an issue: GitHub.