{"record":{"id":"76c4b57479e4aa90","repo":"github/spec-kit","slug":"failed-to-fetch-preset-catalog-from-catalog-url","errorCode":null,"errorMessage":"Failed to fetch preset catalog from {catalog_url}: {e}","messagePattern":"Failed to fetch preset catalog from (.+?): (.+?)","errorType":"exception","errorClass":"PresetError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/presets/__init__.py","lineNumber":4714,"sourceCode":"                    json.dumps(catalog_data, indent=2), encoding=\"utf-8\"\n                )\n\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 (ImportError, Exception) as e:\n            if isinstance(e, PresetError):\n                raise\n            raise PresetError(\n                f\"Failed to fetch preset catalog from {catalog_url}: {e}\"\n            )\n\n    def search(\n        self,\n        query: Optional[str] = None,\n        tag: Optional[str] = None,\n        author: Optional[str] = None,\n    ) -> List[Dict[str, Any]]:\n        \"\"\"Search catalog for presets.\n\n        Searches across all active catalogs (merged by priority) so that\n        community and custom catalogs are included in results.\n\n        Args:\n            query: Search query (searches name, description, tags)\n            tag: Filter by specific tag\n            author: Filter by author name","sourceCodeStart":4696,"sourceCodeEnd":4732,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/presets/__init__.py#L4696-L4732","documentation":"Same failure class as the per-entry fetch, but on the URL-keyed single-catalog fetch path (used by search/list flows): any non-PresetError exception during download, size-limited read, JSON parse, payload validation, or caching of catalog_url is wrapped into PresetError; PresetErrors pass through unchanged.","triggerScenarios":"Calling search()/list flows that fetch a catalog by URL when the request fails (URLError, timeout), the response is not valid JSON, exceeds MAX_JSON_CATALOG_BYTES, fails payload validation, or the URL fails validation after redirects.","commonSituations":"Running preset search on a machine with no/blocked internet; catalog host serving a truncated or oversized payload; stale cached metadata forcing refetch during an outage; SSL interception breaking the TLS handshake.","solutions":["Verify the catalog URL responds with valid JSON: curl -sSL <url> | python -m json.tool","Fix connectivity (network, proxy env vars, certificates) before running preset search","If a redirect target is rejected, pin the config to the final HTTPS URL","Clear the preset cache directory and retry to rule out cache corruption"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"import urllib.request\ntry:\n    urllib.request.urlopen(catalog_url, timeout=10).close()\n    ok = True\nexcept OSError:\n    ok = False  # skip/defer search when offline","typeGuard":null,"tryCatchPattern":"try:\n    results = manager.search(query)\nexcept PresetError as e:\n    if \"Failed to fetch preset catalog\" in str(e):\n        results = cached_results or []   # degrade gracefully offline\n    else:\n        raise","preventionTips":["Cache catalog results so search still works offline","Health-check catalog URLs in config periodically (HEAD request)","Ensure catalog payloads stay within the documented JSON size limit"],"tags":["network","http","presets","catalog","search"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}