github/spec-kit · error · ExtensionError

Failed to fetch catalog from {catalog_url}: {e}

Error message

Failed to fetch catalog from {catalog_url}: {e}

What it means

Legacy/single-catalog fetch path (the one that maintains cache_metadata_file): urllib raised URLError while retrieving catalog_url, and the error is re-raised as an ExtensionError naming the URL. Unlike the multi-catalog merge path, this single-URL fetcher fails hard on the first network error.

Source

Thrown at src/specify_cli/extensions/__init__.py:4119

                self.cache_file.write_text(
                    json.dumps(catalog_data, indent=2), encoding="utf-8"
                )

                # Save cache metadata
                metadata = {
                    "cached_at": datetime.now(timezone.utc).isoformat(),
                    "catalog_url": catalog_url,
                }
                self.cache_metadata_file.write_text(
                    json.dumps(metadata, 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 {catalog_url}: {e}")
        except json.JSONDecodeError as e:
            raise ExtensionError(f"Invalid JSON in catalog: {e}")

    def search(
        self,
        query: Optional[str] = None,
        tag: Optional[str] = None,
        author: Optional[str] = None,
        verified_only: bool = False,
    ) -> List[Dict[str, Any]]:
        """Search catalog for extensions across all active catalogs.

        Args:
            query: Search query (searches name, description, tags)
            tag: Filter by specific tag
            author: Filter by author name
            verified_only: If True, show only verified extensions

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Verify reachability of the exact URL: `curl -vf <catalog_url>`
  2. Fix the catalog_url configuration (scheme, host, port) or point it at a reachable mirror
  3. Configure proxy env vars (HTTPS_PROXY/HTTP_PROXY) if egress requires them
  4. Handle the ExtensionError in the caller to degrade gracefully (empty result + warning) when the catalog is optional
Defensive patterns

Strategy: retry

Validate before calling

import urllib.request

def catalog_url_ok(url: str) -> bool:
    try:
        req = urllib.request.Request(url, method='HEAD')
        with urllib.request.urlopen(req, timeout=5) as r:
            return r.status == 200
    except OSError:
        return False

Try / catch

from specify_cli.extensions import ExtensionError
import time

for attempt in range(3):
    try:
        catalog = catalog_api.fetch()
        break
    except ExtensionError as e:
        if 'Failed to fetch catalog from' in str(e) and attempt < 2:
            time.sleep(2 ** attempt)
            continue
        raise

Prevention

When it happens

Trigger: Calling the catalog search/fetch API that goes through this single-URL path with an unreachable catalog_url — DNS failure, connection refused, TLS handshake failure, or an HTTP error surfaced as URLError.

Common situations: Private/self-hosted catalog server down for maintenance; CI runner without network; corporate proxy blocking the catalog host; wrong scheme or port in the URL.

Related errors


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