{"record":{"id":"d12e8861d062153c","repo":"github/spec-kit","slug":"failed-to-download-from-url-exc","errorCode":null,"errorMessage":"Failed to download from {url}: {exc}","messagePattern":"Failed to download from (.+?): (.+?)","errorType":"exception","errorClass":"ExtensionError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/extensions/_commands.py","lineNumber":160,"sourceCode":"\n        with dl_catalog._open_url(\n            download_url, timeout=60, extra_headers=extra_headers\n        ) as response:\n            archive_data = read_response_limited(\n                response,\n                error_type=ExtensionError,\n                label=f\"extension {url}\",\n            )\n            final_url = (\n                response.geturl() if hasattr(response, \"geturl\") else download_url\n            )\n            content_type = (\n                response.getheader(\"Content-Type\")\n                if hasattr(response, \"getheader\")\n                else None\n            )\n    except urllib.error.URLError as exc:\n        raise ExtensionError(f\"Failed to download from {url}: {exc}\") from exc\n\n    download_fd = -1\n    download_file = None\n    try:\n        try:\n            download_fd = _safe_open_download_zip(\n                project_root, download_dir, archive_filename\n            )\n        except OSError as exc:\n            raise ExtensionError(\n                f\"Could not safely create download file: {exc}\"\n            ) from exc\n\n        try:\n            download_file = os.fdopen(download_fd, \"w+b\")\n            download_fd = -1\n            download_file.write(archive_data)\n            download_file.flush()","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/extensions/_commands.py#L142-L178","documentation":"The bounded (50 MiB) urllib download of the extension archive raised URLError, which is re-raised as an ExtensionError with the URL and cause. This covers every transport-level failure before any bytes can be validated: DNS, refused connections, timeouts, TLS errors, and HTTP error statuses surfaced by urllib.","triggerScenarios":"Running `specify extension install <url>` where the URL is unreachable, the TLS certificate is invalid/untrusted (common behind corporate MITM proxies), the server returns 403/404, or a slow host trips the timeout mid-transfer.","commonSituations":"Signed URL expired (common for pre-signed S3/GitHub release assets); private repo asset requiring auth the installer doesn't carry; CI environments with restricted egress; self-signed certs on internal hosts.","solutions":["Reproduce outside the tool: `curl -vfL <url> -o /dev/null` and read the failure mode","Regenerate expired pre-signed URLs or fix the 403/404 path","For TLS issues: fix the certificate chain or export REQUESTS_CA_PACKAGE-equivalent trust (for urllib: SSL_CERT_FILE pointing at the proxy CA bundle)","Download the archive manually with appropriate credentials, then install from the local file path"],"exampleFix":"# before\nspecify extension install https://github.com/org/repo/releases/download/v1/ext.zip  # 404: asset renamed\n\n# after\nspecify extension install https://github.com/org/repo/releases/download/v1.0.0/my-ext.zip\n# or download then install locally\ncurl -fL -o /tmp/my-ext.zip <url> && specify extension install /tmp/my-ext.zip","handlingStrategy":"retry","validationCode":"import urllib.request\n\ndef url_downloadable(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\n\ntry:\n    manifest = install_from_url_cmd(project_root, url, speckit_version)\nexcept ExtensionError as e:\n    if str(e).startswith('Failed to download from'):\n        # fallback: manual authenticated download, then install local path\n        ...","preventionTips":["Use fresh pre-signed URLs at install time; treat them as short-lived","Keep a manual curl + local-path install as the fallback playbook for auth-gated artifacts"],"tags":["extensions","network","download","urllib"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}