github/spec-kit · error · BundlerError
Invalid catalog url: '{url}'.
Error message
Invalid catalog url: '{url}'. What it means
Raised from the `except ValueError` arm in `add_source()` URL parsing: `urlparse(url)` (or the eager `.hostname`/`.port` access performed inside the try) raised `ValueError`, which in practice means a malformed authority such as a bracketed-but-invalid IPv6 literal (`https://[not-an-ip]/c.json`). The chained exception (`from exc`) preserves the underlying cause. Reading `.hostname` inside the guard is deliberate so this lazy ValueError on Python < 3.14 cannot leak a raw traceback past the CLI's `except BundlerError`.
Source
Thrown at src/specify_cli/bundler/commands_impl/catalog_config.py:158
priority: int,
source_id: str | None = None,
) -> CatalogSource:
url = url.strip()
if not url:
raise BundlerError("A catalog url is required.")
try:
parsed = urlparse(url)
# Read .hostname inside the try: a bracketed-but-invalid IPv6 authority
# (e.g. "https://[not-an-ip]/c.json") parses cleanly under urlparse() on
# Python < 3.14 but raises ValueError lazily on the first .hostname access
# (the raise moved eager into urlparse() only in 3.14). Reading it here
# keeps that ValueError inside the guard instead of leaking a raw
# traceback past the CLI's `except BundlerError`. Reuse the value below.
hostname = parsed.hostname
# Accessing ``port`` performs urllib's syntax/range validation.
_ = parsed.port
except ValueError as exc:
raise BundlerError(f"Invalid catalog url: '{url}'.") from exc
if not (parsed.scheme or parsed.path):
raise BundlerError(f"Invalid catalog url: '{url}'.")
# Reject unsupported URL schemes (e.g. ssh://, ftp://) up front so they are
# never silently canonicalized as local filesystem paths. Local paths that
# merely contain a ':' but no '://' (e.g. Windows drives) are still allowed.
if "://" in url and parsed.scheme.lower() not in _REMOTE_SCHEMES:
raise BundlerError(
f"Unsupported catalog url scheme '{parsed.scheme}://' in '{url}'. "
"Use http(s)://, file://, builtin://, or a local path."
)
if parsed.scheme.lower() in {"http", "https"}:
# Mirror specify_cli.catalogs._validate_catalog_url (#3209/#3210):
# HTTPS only (HTTP just for localhost), and check hostname, not
# netloc — netloc is truthy for host-less URLs like "https://:8080"
# or "https://user@". Validating here keeps junk out of
# bundle-catalogs.yml instead of failing later at fetch time.
is_localhost = hostname in ("localhost", "127.0.0.1", "::1")
if parsed.scheme.lower() != "https" and not is_localhost:View on GitHub (pinned to bf88c9f9a8)
Solutions
- Fix the malformed authority — proper IPv6 form is `https://[2001:db8::1]/c.json`
- Use a plain hostname unless you truly need a literal IP
- Check the chained `__cause__` for the precise urllib parse error
Example fix
# before https://[not-an-ip]/catalog.json https://host:99999/catalog.json # after https://example.com/catalog.json
Defensive patterns
Strategy: try-catch
Validate before calling
from urllib.parse import urlparse
def parses_cleanly(url: str) -> bool:
try:
p = urlparse(url)
_ = p.hostname, p.port # force urllib's lazy validation
return True
except ValueError:
return False Try / catch
from specify_cli.bundler import BundlerError
try:
add_source(project_root, url, policy=policy, priority=50)
except BundlerError as exc:
if "Invalid catalog url" in str(exc) and exc.__cause__ is not ValueError:
# inspect exc.__cause__ for the underlying urllib ValueError detail
raise
raise Prevention
- Url-parse and access .hostname/.port in your own code before passing URLs in
- Use proper [v6-literal] bracket syntax for IPv6 hosts
When it happens
Trigger: URLs with an invalid bracketed IPv6 authority, a port field that fails urllib's range/syntax validation when `.port` is accessed, or other urlparse-level ValueError conditions.
Common situations: Typos in IPv6 literals; unescaped brackets around a hostname; ports out of range (`https://host:99999/`).
Related errors
- Unsupported catalog url scheme '{parsed.scheme}://' in '{url
- Catalog url must be a valid URL with a host: {url}
- Catalog entry '{resolved.entry.id}' has a malformed download
- Malformed catalog config at {path}: expected a mapping at th
- Malformed catalog config at {path}: 'catalogs' must be a lis
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/a7f5bb3189000d67.
Report an issue: GitHub.