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 caches

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Check connectivity to the exact URL in the message: `curl -v <entry.url>`
  2. Fix or remove the broken catalog entry from .specify/extension-catalogs.yml or ~/.specify/extension-catalogs.yml
  3. If behind a proxy, set HTTPS_PROXY; if TLS interception breaks it, install the proxy CA or bypass it
  4. 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

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


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