github/spec-kit · error · PresetError

Preset download URL is malformed: {download_url}

Error message

Preset download URL is malformed: {download_url}

What it means

The pack's download_url exists but is not a string (e.g. a number, list, or dict in the catalog JSON). The downloader type-checks it before URL parsing and raises PresetError with the offending value.

Source

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

                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
            parsed.port
        except ValueError:
            raise PresetError(
                f"Preset download URL is malformed: {download_url}"

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Fix the catalog payload so download_url is a plain string containing the HTTPS URL
  2. If the producer nests URL + checksum, flatten to a string field (and a separate checksum field)
  3. Validate the catalog JSON against its expected schema before publishing

Example fix

// before
{"download_url": {"href": "https://example.com/x.tar.gz"}}

// after
{"download_url": "https://example.com/x.tar.gz"}
Defensive patterns

Strategy: type-guard

Validate before calling

url = info.get("download_url")
if not isinstance(url, str):
    raise ValueError(f"download_url must be a string, got {type(url).__name__}")

Type guard

def is_string_download_url(info: dict) -> bool:
    return isinstance(info.get("download_url"), str)

Try / catch

except PresetError as e:
    if "malformed" in str(e) and not isinstance(url, str):
        # producer-side schema bug; fix catalog JSON, do not retry
        report_catalog_defect(pack_id)
    raise

Prevention

When it happens

Trigger: Catalog payload where download_url is JSON number/boolean/array/object instead of a string — malformed or machine-generated catalog data.

Common situations: Catalog generator serializing an object (e.g. {url: ..., sha: ...}) into download_url; YAML/JSON ambiguity producing a nested structure; schema change on the producer side.

Understand the failure class

Related errors


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