github/spec-kit · error · PresetError

Preset '{pack_id}' has no download URL

Error message

Preset '{pack_id}' has no download URL

What it means

The pack's catalog entry has no download_url at all (falsy value). After the bundled and install_allowed checks, download cannot proceed without a URL, so PresetError is raised.

Source

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

        if pack_info.get("bundled") and not pack_info.get("download_url"):
            from ..extensions import REINSTALL_COMMAND
            raise PresetError(
                f"Preset '{pack_id}' is bundled with spec-kit and has no download URL. "
                f"It should be installed from the local package. "
                f"Use 'specify preset add {pack_id}' to install from the bundled package, "
                f"or reinstall spec-kit if the bundled files are missing: {REINSTALL_COMMAND}"
            )

        if not pack_info.get("_install_allowed", True):
            catalog_name = pack_info.get("_catalog_name", "unknown")
            raise PresetError(
                f"Preset '{pack_id}' is from the '{catalog_name}' catalog which does not allow installation. "
                f"Use --from with the preset's repository URL instead."
            )

        download_url = pack_info.get("download_url")
        if not download_url:
            raise PresetError(
                f"Preset '{pack_id}' has no download URL"
            )
        if not isinstance(download_url, str):
            raise PresetError(
                f"Preset download URL is malformed: {download_url}"
            )

        from urllib.parse import urlparse

        # A malformed authority (e.g. an unterminated IPv6 bracket
        # "https://[::1") makes urlparse / hostname access raise ValueError.
        # The download_url comes from catalog payload data, so surface a clean
        # PresetError rather than leaking a raw ValueError past the command
        # handler (which only catches PresetError). Mirrors catalogs (#3435)
        # and workflows/catalog.py (#3484).
        try:
            parsed = urlparse(download_url)
            hostname = parsed.hostname

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Fix the catalog payload: add a valid HTTPS download_url to the pack entry
  2. If the pack is meant to be local-only, mark it 'bundled' so callers take the local-install path
  3. Report the broken entry to the catalog maintainer if you do not control it

Example fix

// before (catalog entry)
{"id": "mytheme"}

// after
{"id": "mytheme", "download_url": "https://example.com/mytheme-1.0.0.tar.gz"}
Defensive patterns

Strategy: validation

Validate before calling

info = manager.get_pack_info(pack_id) or {}
if not info.get("download_url") and not info.get("bundled"):
    raise ValueError(f"catalog entry for {pack_id} lacks download_url")

Type guard

def has_download_url(info: dict) -> bool:
    return bool(info.get("download_url")) or bool(info.get("bundled"))

Try / catch

except PresetError as e:
    if "has no download URL" in str(e):
        # upstream catalog data defect; report rather than retry
        report_catalog_defect(pack_id)
    raise

Prevention

When it happens

Trigger: A catalog entry that is not 'bundled' but omits download_url (or sets it to an empty string / null) — i.e. malformed third-party catalog data.

Common situations: Hand-authored catalog JSON missing the download_url field; catalog schema drift where the producer renamed the field; entry intentionally metadata-only being passed to the download path.

Related errors


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