{"record":{"id":"b9b1b7c543905acf","repo":"github/spec-kit","slug":"failed-to-fetch-catalog-from-catalog-url-e","errorCode":null,"errorMessage":"Failed to fetch catalog from {catalog_url}: {e}","messagePattern":"Failed to fetch catalog from (.+?): (.+?)","errorType":"exception","errorClass":"ExtensionError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/extensions/__init__.py","lineNumber":4119,"sourceCode":"                self.cache_file.write_text(\n                    json.dumps(catalog_data, indent=2), encoding=\"utf-8\"\n                )\n\n                # Save cache metadata\n                metadata = {\n                    \"cached_at\": datetime.now(timezone.utc).isoformat(),\n                    \"catalog_url\": catalog_url,\n                }\n                self.cache_metadata_file.write_text(\n                    json.dumps(metadata, indent=2), encoding=\"utf-8\"\n                )\n            except OSError:\n                pass  # Cache is best-effort; proceed with fetched data\n\n            return catalog_data\n\n        except urllib.error.URLError as e:\n            raise ExtensionError(f\"Failed to fetch catalog from {catalog_url}: {e}\")\n        except json.JSONDecodeError as e:\n            raise ExtensionError(f\"Invalid JSON in catalog: {e}\")\n\n    def search(\n        self,\n        query: Optional[str] = None,\n        tag: Optional[str] = None,\n        author: Optional[str] = None,\n        verified_only: bool = False,\n    ) -> List[Dict[str, Any]]:\n        \"\"\"Search catalog for extensions across all active catalogs.\n\n        Args:\n            query: Search query (searches name, description, tags)\n            tag: Filter by specific tag\n            author: Filter by author name\n            verified_only: If True, show only verified extensions\n","sourceCodeStart":4101,"sourceCodeEnd":4137,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/extensions/__init__.py#L4101-L4137","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Verify reachability of the exact URL: `curl -vf <catalog_url>`","Fix the catalog_url configuration (scheme, host, port) or point it at a reachable mirror","Configure proxy env vars (HTTPS_PROXY/HTTP_PROXY) if egress requires them","Handle the ExtensionError in the caller to degrade gracefully (empty result + warning) when the catalog is optional"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"import urllib.request\n\ndef catalog_url_ok(url: str) -> bool:\n    try:\n        req = urllib.request.Request(url, method='HEAD')\n        with urllib.request.urlopen(req, timeout=5) as r:\n            return r.status == 200\n    except OSError:\n        return False","typeGuard":null,"tryCatchPattern":"from specify_cli.extensions import ExtensionError\nimport time\n\nfor attempt in range(3):\n    try:\n        catalog = catalog_api.fetch()\n        break\n    except ExtensionError as e:\n        if 'Failed to fetch catalog from' in str(e) and attempt < 2:\n            time.sleep(2 ** attempt)\n            continue\n        raise","preventionTips":["Add a reachability check before batch operations that hit the catalog","Monitor self-hosted catalog servers and keep a mirror URL on standby"],"tags":["extensions","catalog","network","urllib"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}