github/spec-kit · error · PresetValidationError
Catalog URL must be a valid URL with a host.
Error message
Catalog URL must be a valid URL with a host.
What it means
The catalog URL parsed successfully but has no hostname (parsed.hostname is None). The validator deliberately checks hostname rather than netloc because netloc is truthy for host-less URLs like `https://:8080` or `https://user@`; per issue #3209, only a real hostname guarantees the URL has a host to connect to.
Source
Thrown at src/specify_cli/presets/__init__.py:4164
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.
Delegates to :func:`specify_cli.authentication.http.build_request`.
"""
from specify_cli.authentication.http import build_request
return build_request(url)
def _open_url(
self,
url: str,
timeout: int = 10,
extra_headers: Optional[Dict[str, str]] = None,
redirect_validator=None,
):View on GitHub (pinned to bf88c9f9a8)
Solutions
- Include an explicit hostname: `https://catalog.example.com/catalog.json`.
- Check interpolated values (env vars, config keys) are non-empty before building the URL.
- Pre-validate with urllib.parse: assert urlparse(url).hostname is not None.
Example fix
# before - url: "https://:8443/catalog.json" # after - url: "https://catalog.example.com:8443/catalog.json"
Defensive patterns
Strategy: validation
Validate before calling
from urllib.parse import urlparse
assert urlparse(url).hostname, f"catalog URL {url!r} has no host" Type guard
from urllib.parse import urlparse
def url_has_host(url: str) -> bool:
try:
return urlparse(url).hostname is not None
except ValueError:
return False Try / catch
try:
manager.add_catalog(url)
except PresetValidationError as e:
if "valid URL with a host" in str(e):
# re-check templated env vars; rebuild as scheme://host[:port]/path
... Prevention
- Check hostname, not netloc — 'https://:8080' and 'https://user@' look truthy but have no host (#3209).
- Assert interpolated env vars are non-empty before building URLs.
- Test catalog URLs once in CI: urlparse(url).hostname is not None.
When it happens
Trigger: Catalog URLs such as `https://:8080/catalog.json`, `https://user@/catalog.json`, `https:///path/catalog.json` (empty host), or scheme-only strings like `https://` — urlparse succeeds but hostname is None.
Common situations: Env-var templating that drops the hostname (`https://${HOST}/...` with HOST unset), hand-editing preset-catalogs.yml and deleting the host, or URL-building code that concatenates an empty host.
Related errors
- Catalog URL is malformed: {url}
- Invalid catalog url: '{url}'.
- 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
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/a0e56454489a070a.
Report an issue: GitHub.