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
- Fix the catalog payload so download_url is a plain string containing the HTTPS URL
- If the producer nests URL + checksum, flatten to a string field (and a separate checksum field)
- 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
- Enforce string type for URL fields in catalog producers/JSON schemas
- Never serialize structured objects into URL fields
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Failed to fetch preset catalog from {entry.url}: {e}
- Failed to fetch preset catalog from {catalog_url}: {e}
- Preset '{pack_id}' not found in catalog
- Preset '{pack_id}' is from the '{catalog_name}' catalog whic
- Preset '{pack_id}' has no download URL
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/593fd00fc2613615.
Report an issue: GitHub.