{"record":{"id":"6f6492f7732d28c5","repo":"github/spec-kit","slug":"catalog-url-is-malformed-url","errorCode":null,"errorMessage":"Catalog URL is malformed: {url}","messagePattern":"Catalog URL is malformed: (.+?)","errorType":"validation","errorClass":"PresetValidationError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/presets/__init__.py","lineNumber":4151,"sourceCode":"        self.cache_file = self.cache_dir / \"catalog.json\"\n        self.cache_metadata_file = self.cache_dir / \"catalog-metadata.json\"\n\n    def _validate_catalog_url(self, url: str) -> None:\n        \"\"\"Validate that a catalog URL uses HTTPS (localhost HTTP allowed).\n\n        Args:\n            url: URL to validate\n\n        Raises:\n            PresetValidationError: If URL is invalid or uses non-HTTPS scheme\n        \"\"\"\n        from urllib.parse import urlparse\n\n        try:\n            parsed = urlparse(url)\n            hostname = parsed.hostname\n        except ValueError:\n            raise PresetValidationError(f\"Catalog URL is malformed: {url}\") from None\n        is_localhost = hostname in (\"localhost\", \"127.0.0.1\", \"::1\")\n        if parsed.scheme != \"https\" and not (\n            parsed.scheme == \"http\" and is_localhost\n        ):\n            raise PresetValidationError(\n                f\"Catalog URL must use HTTPS (got {parsed.scheme}://). \"\n                \"HTTP is only allowed for localhost.\"\n            )\n        # Check hostname, not netloc: netloc is truthy for host-less URLs like\n        # \"https://:8080\" or \"https://user@\", so the host guarantee this error\n        # promises would not actually hold. hostname is None in those cases (#3209).\n        if not hostname:\n            raise PresetValidationError(\n                \"Catalog URL must be a valid URL with a host.\"\n            )\n\n    def _make_request(self, url: str):\n        \"\"\"Build a urllib Request, adding auth headers when a provider matches.","sourceCodeStart":4133,"sourceCodeEnd":4169,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/presets/__init__.py#L4133-L4169","documentation":"Raised while validating a catalog URL: urllib.parse.urlparse raised ValueError while parsing, meaning the URL string is malformed enough that even parsing fails (e.g. invalid characters in the scheme/host, unmatched brackets). Reported as PresetValidationError with the offending URL.","triggerScenarios":"Passing a catalog URL containing an invalid IPv6 literal (`http://[::1`), a URL with a bad scheme delimiter, or non-URL garbage that trips urlparse's parser rather than merely producing empty components.","commonSituations":"Typos in preset-catalogs.yml or CLI --catalog arguments, copy-paste artifacts (trailing brackets, control characters), or templating that mangles the URL string.","solutions":["Fix the URL string in preset-catalogs.yml (or the CLI argument) — ensure scheme://host[:port]/path form.","For IPv6, use proper bracketed literals: `https://[::1]:8443/`.","Sanitize programmatic inputs (strip whitespace/quotes) before treating them as URLs."],"exampleFix":"# before\ncatalogs:\n  - url: \"https://[::1:8443/catalog.json\"\n\n# after\ncatalogs:\n  - url: \"https://[::1]:8443/catalog.json\"","handlingStrategy":"validation","validationCode":"from urllib.parse import urlparse\ntry:\n    parsed = urlparse(url)\nexcept ValueError:\n    raise ValueError(f\"malformed catalog URL: {url!r}\")","typeGuard":null,"tryCatchPattern":"try:\n    manager.add_catalog(url)  # or the validating call\nexcept PresetValidationError as e:\n    if \"malformed\" in str(e):\n        # re-enter the URL; check for truncated IPv6 brackets or stray characters\n        ...","preventionTips":["Sanitize URLs from config/CLI input (strip whitespace and quotes).","Bracket IPv6 literals correctly: https://[::1]:8443/.","Pre-validate with urlparse before writing catalog config."],"tags":["preset","catalog","url","validation"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}