{"record":{"id":"593fd00fc2613615","repo":"github/spec-kit","slug":"preset-download-url-is-malformed-download-url","errorCode":null,"errorMessage":"Preset download URL is malformed: {download_url}","messagePattern":"Preset download URL is malformed: (.+?)","errorType":"exception","errorClass":"PresetError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/presets/__init__.py","lineNumber":4850,"sourceCode":"                f\"It should be installed from the local package. \"\n                f\"Use 'specify preset add {pack_id}' to install from the bundled package, \"\n                f\"or reinstall spec-kit if the bundled files are missing: {REINSTALL_COMMAND}\"\n            )\n\n        if not pack_info.get(\"_install_allowed\", True):\n            catalog_name = pack_info.get(\"_catalog_name\", \"unknown\")\n            raise PresetError(\n                f\"Preset '{pack_id}' is from the '{catalog_name}' catalog which does not allow installation. \"\n                f\"Use --from with the preset's repository URL instead.\"\n            )\n\n        download_url = pack_info.get(\"download_url\")\n        if not download_url:\n            raise PresetError(\n                f\"Preset '{pack_id}' has no download URL\"\n            )\n        if not isinstance(download_url, str):\n            raise PresetError(\n                f\"Preset download URL is malformed: {download_url}\"\n            )\n\n        from urllib.parse import urlparse\n\n        # A malformed authority (e.g. an unterminated IPv6 bracket\n        # \"https://[::1\") makes urlparse / hostname access raise ValueError.\n        # The download_url comes from catalog payload data, so surface a clean\n        # PresetError rather than leaking a raw ValueError past the command\n        # handler (which only catches PresetError). Mirrors catalogs (#3435)\n        # and workflows/catalog.py (#3484).\n        try:\n            parsed = urlparse(download_url)\n            hostname = parsed.hostname\n            parsed.port\n        except ValueError:\n            raise PresetError(\n                f\"Preset download URL is malformed: {download_url}\"","sourceCodeStart":4832,"sourceCodeEnd":4868,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/presets/__init__.py#L4832-L4868","documentation":"The pack's download_url exists but is not a string (e.g. a number, list, or dict in the catalog JSON). The downloader type-checks it before URL parsing and raises PresetError with the offending value.","triggerScenarios":"Catalog payload where download_url is JSON number/boolean/array/object instead of a string — malformed or machine-generated catalog data.","commonSituations":"Catalog generator serializing an object (e.g. {url: ..., sha: ...}) into download_url; YAML/JSON ambiguity producing a nested structure; schema change on the producer side.","solutions":["Fix the catalog payload so download_url is a plain string containing the HTTPS URL","If the producer nests URL + checksum, flatten to a string field (and a separate checksum field)","Validate the catalog JSON against its expected schema before publishing"],"exampleFix":"// before\n{\"download_url\": {\"href\": \"https://example.com/x.tar.gz\"}}\n\n// after\n{\"download_url\": \"https://example.com/x.tar.gz\"}","handlingStrategy":"type-guard","validationCode":"url = info.get(\"download_url\")\nif not isinstance(url, str):\n    raise ValueError(f\"download_url must be a string, got {type(url).__name__}\")","typeGuard":"def is_string_download_url(info: dict) -> bool:\n    return isinstance(info.get(\"download_url\"), str)","tryCatchPattern":"except PresetError as e:\n    if \"malformed\" in str(e) and not isinstance(url, str):\n        # producer-side schema bug; fix catalog JSON, do not retry\n        report_catalog_defect(pack_id)\n    raise","preventionTips":["Enforce string type for URL fields in catalog producers/JSON schemas","Never serialize structured objects into URL fields"],"tags":["presets","catalog","type-mismatch"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}