github/spec-kit · error · BundlerError

A catalog url is required.

Error message

A catalog url is required.

What it means

Raised by `add_source()` when the `url` argument is empty after stripping. Adding a catalog source requires a URL pointing at the catalog (http(s)://, file://, builtin://, or a local path); a blank one cannot proceed.

Source

Thrown at src/specify_cli/bundler/commands_impl/catalog_config.py:145

        host = parsed.hostname or ""
        path_stem = Path(parsed.path).stem if parsed.path else ""
        parts = [p for p in (_slug(host), _slug(path_stem)) if p]
        return "-".join(parts) or "catalog"
    stem = Path(parsed.path or url).stem
    return _slug(stem) or "catalog"


def add_source(
    project_root: Path,
    url: str,
    *,
    policy: str,
    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.

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Supply a non-empty catalog URL (https://…, file://…, builtin://…, or a local path)
  2. Guard the caller: fail early if the URL variable is empty before invoking add
  3. Use shell defaults like `${CATALOG_URL:?CATALOG_URL required}`

Example fix

# before
specify bundle catalog add "$MAYBE_UNSET_URL" --policy auto

# after
specify bundle catalog add "${CATALOG_URL:?CATALOG_URL required}" --policy auto
Defensive patterns

Strategy: validation

Validate before calling

url = (url or "").strip()
if not url:
    raise SystemExit("catalog URL is required (https://…, file://…, builtin://…, or local path)")

Type guard

def is_nonempty_url(url: object) -> bool:
    return isinstance(url, str) and bool(url.strip())

Try / catch

try:
    add_source(project_root, url, policy=policy, priority=50)
except BundlerError as exc:
    if "A catalog url is required" in str(exc):
        # re-prompt for the URL; do not silently pass ""
        raise
    raise

Prevention

When it happens

Trigger: Calling the catalog-add API with `url=""`, `url=" "`, or a variable that expanded to nothing.

Common situations: Shell script passing an unset `$CATALOG_URL` without `set -u`/defaults; CLI invocation missing the positional URL argument in a wrapper; CI variable name typo yielding an empty string.

Related errors


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