{"record":{"id":"25e88fe2239c4ae3","repo":"github/spec-kit","slug":"invalid-catalog-format-from-entry-url-shape-er","errorCode":null,"errorMessage":"Invalid catalog format from {entry.url}: {shape_error}","messagePattern":"Invalid catalog format from (.+?): (.+?)","errorType":"console","errorClass":"IntegrationCatalogError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/integrations/catalog.py","lineNumber":215,"sourceCode":"            from specify_cli.authentication.http import open_url\n\n            with open_url(entry.url, timeout=10) as resp:\n                # Validate final URL after redirects\n                final_url = resp.geturl()\n                if final_url != entry.url:\n                    self._validate_catalog_url(final_url)\n                catalog_data = json.loads(\n                    read_response_limited(\n                        resp,\n                        max_bytes=MAX_JSON_METADATA_BYTES,\n                        error_type=IntegrationCatalogError,\n                        label=f\"catalog from {entry.url}\",\n                    ).decode(\"utf-8\")\n                )\n\n            shape_error = _catalog_shape_error(catalog_data)\n            if shape_error is not None:\n                raise IntegrationCatalogError(\n                    f\"Invalid catalog format from {entry.url}: {shape_error}\"\n                )\n\n            try:\n                self.cache_dir.mkdir(parents=True, exist_ok=True)\n                cache_file.write_text(json.dumps(catalog_data, indent=2), encoding=\"utf-8\")\n                cache_meta.write_text(\n                    json.dumps(\n                        {\n                            \"cached_at\": datetime.now(timezone.utc).isoformat(),\n                            \"catalog_url\": entry.url,\n                        },\n                        indent=2,\n                    ),\n                    encoding=\"utf-8\",\n                )\n            except OSError:\n                pass  # Cache is best-effort; proceed with fetched data","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/integrations/catalog.py#L197-L233","documentation":"The community integration catalog fetcher downloads a JSON catalog (with a byte cap via read_response_limited), parses it, then runs _catalog_shape_error to validate its structure. If the JSON does not match the expected catalog schema, IntegrationCatalogError is raised with the specific shape problem and the source URL. This catches truncated downloads, HTML error pages parsed as JSON edge cases, and out-of-date or hand-edited catalogs before anything is cached or installed.","triggerScenarios":"The catalog URL returns a valid JSON object that is missing required keys or has wrong types (e.g. integrations is an object instead of a list); a redirect landing on a sign-in page or an API error payload; a truncated body within the byte cap; a feed that changed schema in a newer/older version than this client understands.","commonSituations":"Pointing the catalog URL at a fork with a subtly different schema; the upstream catalog format changing while the local specify CLI is old; corporate proxies returning JSON error documents with 200 status; typos in the URL landing on a valid-JSON-but-wrong endpoint.","solutions":["Fetch the URL manually (curl -sL <url> | python -m json.tool) and compare its top-level structure against a known-good catalog — the shape_error text names what is wrong.","Update the specify CLI to the latest version so its _catalog_shape_error expectations match the current catalog schema.","If the URL is configurable, point it back at the official catalog or fix the forked catalog's JSON to the expected shape."],"exampleFix":"# before: fork's catalog is {\"entries\": {...}}  # wrong key/type\n# after: conform to the expected shape, e.g.\n{\"integrations\": [{\"key\": \"my-agent\", \"source\": \"...\", \"description\": \"...\"}]}","handlingStrategy":"try-catch","validationCode":"import json, urllib.request\n\ndef catalog_looks_valid(url: str) -> bool:\n    try:\n        with urllib.request.urlopen(url, timeout=10) as r:\n            data = json.loads(r.read(1_048_576).decode(\"utf-8\"))\n    except Exception:\n        return False\n    return isinstance(data, dict) and isinstance(data.get(\"integrations\"), list)","typeGuard":null,"tryCatchPattern":"from specify_cli.integrations.catalog import IntegrationCatalogError\n\ntry:\n    entries = catalog.fetch()\nexcept IntegrationCatalogError as exc:\n    if \"Invalid catalog format\" in str(exc):\n        # fall back to the last cached catalog or the default URL\n        entries = catalog.load_cached() or catalog.fetch_default()\n    else:\n        raise","preventionTips":["Pin the catalog URL to the official source unless maintaining a schema-compatible fork.","Keep the specify CLI updated so catalog schema expectations stay in sync with upstream.","Retain the last known-good cached catalog so a bad upstream payload degrades gracefully."],"tags":["network","json","validation","catalog","integrations"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}