github/spec-kit · error · BundlerError
Catalog entry '{resolved.entry.id}' has no download_url; can
Error message
Catalog entry '{resolved.entry.id}' has no download_url; cannot resolve its manifest. What it means
_download_manifest fetches a catalog-resolved bundle's manifest from its entry.download_url; this fires when the catalog entry carries no download_url at all, so there is nothing to fetch. It is a data-quality error in the catalog (or its cached copy), not in the user's project.
Source
Thrown at src/specify_cli/commands/bundle/__init__.py:853
def _download_manifest(resolved, *, offline: bool):
"""Resolve a bundle's manifest from its catalog ``download_url``.
Catalog ``download_url``s are HTTPS-only (``http`` allowed for localhost),
matching the extensions/presets/workflows catalog systems. Remote URLs are
fetched with the shared authenticated, redirect-validated HTTP client, and
only when not ``--offline``.
Local and ``file://`` sources are intentionally not resolved here: to
install a bundle from disk, pass the path positionally
(``specify bundle install ./path/to/bundle.yml`` — a bundle directory or a
``.zip`` artifact also works), which :func:`_local_manifest_source` handles
before catalog resolution and which never touches ``download_url``.
"""
from urllib.parse import urlparse
url = resolved.entry.download_url
if not url:
raise BundlerError(
f"Catalog entry '{resolved.entry.id}' has no download_url; cannot resolve "
"its manifest."
)
# A malformed authority (e.g. an unclosed IPv6 bracket ``https://[::1``)
# makes urlparse raise ValueError. Surface it as the documented
# BundlerError, like the sibling ``_validate_remote_url``, rather than
# leaking a raw ValueError past the callers, which only catch BundlerError.
try:
parsed = urlparse(url)
except ValueError:
raise BundlerError(
f"Catalog entry '{resolved.entry.id}' has a malformed download_url: {url}"
) from None
scheme = parsed.scheme.lower()
# ``file://`` URLs and bare filesystem paths (including Windows drive paths
# like ``C:\bundle.yml``, which urlparse reads as a single-letter scheme)
# are not valid catalog download URLs. Catalog URLs are HTTPS-only acrossView on GitHub (pinned to bf88c9f9a8)
Solutions
- Install from a concrete artifact instead: specify bundle install <bundle.yml | dir | .zip>.
- Fix the catalog entry by adding its download_url and refresh/re-cache the catalog.
- Report to the catalog maintainer if it is a third-party index.
Example fix
# before (catalog entry)
{"id": "my-bundle", "name": "My Bundle"}
# after
{"id": "my-bundle", "name": "My Bundle", "download_url": "https://example.com/my-bundle.zip"} Defensive patterns
Strategy: validation
Validate before calling
resolved = stack.resolve(bundle_id)
if not resolved.entry.download_url:
raise SystemExit(f"Catalog entry '{resolved.entry.id}' lacks download_url; install from a path instead") Type guard
def entry_is_downloadable(resolved) -> bool:
return bool(getattr(resolved.entry, "download_url", None)) Try / catch
try:
manifest = _download_manifest(resolved, offline=offline)
except BundlerError as exc:
if "no download_url" in str(exc):
# install from local artifact; report catalog data issue
... Prevention
- Validate catalog entries (download_url present and https) at publish time.
- Keep local artifact copies of catalog bundles you depend on.
When it happens
Trigger: stack.resolve(bundle_id) succeeds and _download_manifest is called, but resolved.entry.download_url is falsy — a registry row published without a download link.
Common situations: Curated catalogs with incomplete entries; hand-edited catalog JSON/YAML missing the field; a bundle indexed for discovery only but accidentally resolvable from an install-allowed source.
Related errors
- Malformed catalog config at {path}: expected a mapping at th
- Unsupported catalog config schema version '{str(schema_versi
- Malformed catalog config at {path}: 'catalogs' must be a lis
- Malformed catalog config at {path}: each catalog entry must
- A catalog url is required.
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/010d06e01c097782.
Report an issue: GitHub.