github/spec-kit · error · BundlerError
Unsupported catalog url scheme '{parsed.scheme}://' in '{url
Error message
Unsupported catalog url scheme '{parsed.scheme}://' in '{url}'. Use http(s)://, file://, builtin://, or a local path. What it means
Raised when the URL contains `://` but its scheme is not in `_REMOTE_SCHEMES` (http/https, file, builtin). Unsupported schemes like `ssh://` or `ftp://` are rejected up front so they are never silently canonicalized into local filesystem paths. Local paths containing `:` without `://` (Windows drive letters like `C:\catalog.json`) are explicitly still allowed.
Source
Thrown at src/specify_cli/bundler/commands_impl/catalog_config.py:165
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:
raise BundlerError(
f"Catalog url must use HTTPS (got {parsed.scheme}://). "
"HTTP is only allowed for localhost."
)
if not hostname:
raise BundlerError(f"Catalog url must be a valid URL with a host: {url}")
View on GitHub (pinned to bf88c9f9a8)
Solutions
- Serve the catalog over http(s)://, file://, builtin://, or point at a local file path
- For git-hosted catalogs, use the raw/https URL of the JSON file, not the clone URL
- For local files use a plain path (POSIX) — no scheme needed
Example fix
# before ssh://git@example.com/spec-kit/catalogs.json git://example.com/catalogs.json # after https://example.com/catalogs.json file:///opt/spec-kit/catalogs.json
Defensive patterns
Strategy: validation
Validate before calling
from urllib.parse import urlparse
REMOTE_SCHEMES = {"http", "https", "file", "builtin"}
u = url.strip()
p = urlparse(u)
if "://" in u and p.scheme.lower() not in REMOTE_SCHEMES:
raise SystemExit(f"scheme {p.scheme!r} unsupported; use http(s)://, file://, builtin://, or a path") Type guard
def is_supported_catalog_url(url: str) -> bool:
u = url.strip()
p = urlparse(u)
return "://" not in u or p.scheme.lower() in {"http", "https", "file", "builtin"} Try / catch
try:
add_source(project_root, url, policy=policy, priority=50)
except BundlerError as exc:
if "Unsupported catalog url scheme" in str(exc):
# swap git/ssh clone URLs for the raw https/file URL of the catalog JSON
raise
raise Prevention
- Catalogs are fetched as files — always point at the raw JSON URL, never a git clone URL
- Windows drive paths (C:\…) are fine; only x:// schemes are scheme-checked
When it happens
Trigger: `ssh://git@host/catalog.json`, `ftp://host/c.json`, `git://...`, or any `x://...` URL whose scheme is not in `_REMOTE_SCHEMES`.
Common situations: Pasting a git clone URL instead of the raw catalog JSON URL; internal tooling URLs using custom schemes; copying from an Ansible/CI variable that expects scp-style syntax.
Related errors
- Invalid catalog url: '{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/abfcc413c069e6c7.
Report an issue: GitHub.