github/spec-kit · error · ExtensionError
Failed to fetch catalog from {entry.url}: {e}
Error message
Failed to fetch catalog from {entry.url}: {e} What it means
Raised during a multi-catalog fetch when urllib raises URLError while retrieving one catalog entry (entry.url) — DNS failure, refused connection, timeout, TLS error, or HTTP error surfaced as URLError. The error names the failing URL so the user can tell which catalog in the stacked config is unreachable.
Source
Thrown at src/specify_cli/extensions/__init__.py:3924
json.dumps(catalog_data, indent=2), encoding="utf-8"
)
cache_meta_file.write_text(
json.dumps(
{
"cached_at": datetime.now(timezone.utc).isoformat(),
"catalog_url": entry.url,
},
indent=2,
),
encoding="utf-8",
)
except OSError:
pass # Cache is best-effort; proceed with fetched data
return catalog_data
except urllib.error.URLError as e:
raise ExtensionError(f"Failed to fetch catalog from {entry.url}: {e}")
except json.JSONDecodeError as e:
raise ExtensionError(f"Invalid JSON in catalog from {entry.url}: {e}")
def _get_merged_extensions(
self, force_refresh: bool = False
) -> List[Dict[str, Any]]:
"""Fetch and merge extensions from all active catalogs.
Higher-priority (lower priority number) catalogs win on conflicts
(same extension id in two catalogs). Each extension dict is annotated with:
- _catalog_name: name of the source catalog
- _install_allowed: whether installation is allowed from this catalog
Catalogs that fail to fetch are skipped. Raises ExtensionError only if
ALL catalogs fail.
Args:
force_refresh: If True, bypass all cachesView on GitHub (pinned to bf88c9f9a8)
Solutions
- Check connectivity to the exact URL in the message: `curl -v <entry.url>`
- Fix or remove the broken catalog entry from .specify/extension-catalogs.yml or ~/.specify/extension-catalogs.yml
- If behind a proxy, set HTTPS_PROXY; if TLS interception breaks it, install the proxy CA or bypass it
- Work offline by ensuring at least the built-in default catalog is reachable, or point SPECKIT_CATALOG_URL at a local file/http://localhost mirror (localhost HTTP is allowed)
Defensive patterns
Strategy: try-catch
Validate before calling
import urllib.request
def catalog_reachable(url: str) -> bool:
try:
with urllib.request.urlopen(url, timeout=5) as r:
return r.status == 200
except OSError:
return False Try / catch
from specify_cli.extensions import ExtensionError
try:
extensions = manager._get_merged_extensions()
except ExtensionError as e:
if str(e).startswith('Failed to fetch catalog from'):
# one catalog in the stack failed; drop it from config and retry
... Prevention
- Keep a locally mirrored catalog URL as a fallback entry in extension-catalogs.yml
- Verify custom catalog URLs with curl before committing the config
When it happens
Trigger: Any active catalog URL (project .specify/extension-catalogs.yml, user ~/.specify/extension-catalogs.yml, or built-in defaults) being unreachable: `specify extension list/search` or any catalog-consuming command while offline or behind a blocking proxy.
Common situations: Working offline or in a sandboxed CI with no egress; a self-hosted catalog server down; corporate MITM proxy rejecting TLS; typo'd hostname; IPv6-only misconfiguration. Note the fetch loop skips failed catalogs and only raises 'Failed to fetch any extension catalog' if all fail — this specific error propagates when the fetch result is consumed directly.
Related errors
- Failed to fetch catalog from {catalog_url}: {e}
- Could not query extension catalog: {catalog_error}
- Invalid JSON in catalog from {entry.url}: {e}
- Failed to fetch any extension catalog
- Invalid JSON in catalog: {e}
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/528f91f0b640aa84.
Report an issue: GitHub.