github/spec-kit · error · PresetValidationError

Catalog URL is malformed: {url}

Error message

Catalog URL is malformed: {url}

What it means

Raised while validating a catalog URL: urllib.parse.urlparse raised ValueError while parsing, meaning the URL string is malformed enough that even parsing fails (e.g. invalid characters in the scheme/host, unmatched brackets). Reported as PresetValidationError with the offending URL.

Source

Thrown at src/specify_cli/presets/__init__.py:4151

        self.cache_file = self.cache_dir / "catalog.json"
        self.cache_metadata_file = self.cache_dir / "catalog-metadata.json"

    def _validate_catalog_url(self, url: str) -> None:
        """Validate that a catalog URL uses HTTPS (localhost HTTP allowed).

        Args:
            url: URL to validate

        Raises:
            PresetValidationError: If URL is invalid or uses non-HTTPS scheme
        """
        from urllib.parse import urlparse

        try:
            parsed = urlparse(url)
            hostname = parsed.hostname
        except ValueError:
            raise PresetValidationError(f"Catalog 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 PresetValidationError(
                f"Catalog URL must use HTTPS (got {parsed.scheme}://). "
                "HTTP is only allowed for localhost."
            )
        # Check hostname, not netloc: netloc is truthy for host-less URLs like
        # "https://:8080" or "https://user@", so the host guarantee this error
        # promises would not actually hold. hostname is None in those cases (#3209).
        if not hostname:
            raise PresetValidationError(
                "Catalog URL must be a valid URL with a host."
            )

    def _make_request(self, url: str):
        """Build a urllib Request, adding auth headers when a provider matches.

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Fix the URL string in preset-catalogs.yml (or the CLI argument) — ensure scheme://host[:port]/path form.
  2. For IPv6, use proper bracketed literals: `https://[::1]:8443/`.
  3. Sanitize programmatic inputs (strip whitespace/quotes) before treating them as URLs.

Example fix

# before
catalogs:
  - url: "https://[::1:8443/catalog.json"

# after
catalogs:
  - url: "https://[::1]:8443/catalog.json"
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlparse
try:
    parsed = urlparse(url)
except ValueError:
    raise ValueError(f"malformed catalog URL: {url!r}")

Try / catch

try:
    manager.add_catalog(url)  # or the validating call
except PresetValidationError as e:
    if "malformed" in str(e):
        # re-enter the URL; check for truncated IPv6 brackets or stray characters
        ...

Prevention

When it happens

Trigger: Passing a catalog URL containing an invalid IPv6 literal (`http://[::1`), a URL with a bad scheme delimiter, or non-URL garbage that trips urlparse's parser rather than merely producing empty components.

Common situations: Typos in preset-catalogs.yml or CLI --catalog arguments, copy-paste artifacts (trailing brackets, control characters), or templating that mangles the URL string.

Understand the failure class

Related errors


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