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

  1. Fix the malformed authority — proper IPv6 form is `https://[2001:db8::1]/c.json`
  2. Use a plain hostname unless you truly need a literal IP
  3. 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

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


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