{"record":{"id":"27e27bd924895749","repo":"github/spec-kit","slug":"url-did-not-return-a-zip-archive-or-tar-gz-tgz-a","errorCode":null,"errorMessage":"{url} did not return a ZIP archive or tar.gz/tgz archive (got {len(archive_data)} bytes). This usually means the request was not authenticated and a login/HTML page was returned. Verify the URL and configured credentials.","messagePattern":"(.+?) did not return a ZIP archive or tar\\.gz/tgz archive \\(got (.+?) bytes\\)\\. This usually means the request was not authenticated and a login/HTML page was returned\\. Verify the URL and configured credentials\\.","errorType":"exception","errorClass":"ExtensionError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/extensions/_commands.py","lineNumber":199,"sourceCode":"            raise ExtensionError(\n                f\"Could not safely write download file: {exc}\"\n            ) from exc\n\n        format_source = (\n            final_url\n            if archive_format_from_name(final_url) is not None\n            else url\n        )\n        try:\n            detect_archive_format(\n                archive_path,\n                archive_file=download_file,\n                source_name=format_source,\n                content_type=content_type,\n                error_type=ExtensionError,\n            )\n        except ExtensionError as exc:\n            raise ExtensionError(\n                f\"{url} did not return a ZIP archive or tar.gz/tgz archive \"\n                f\"(got {len(archive_data)} bytes). This usually means the request \"\n                \"was not authenticated and a login/HTML page was returned. \"\n                \"Verify the URL and configured credentials.\"\n            ) from exc\n\n        # Consume the transient inode reserved above rather than reopening the\n        # cache pathname during extraction.\n        try:\n            return manager.install_from_zip(\n                archive_path,\n                speckit_version,\n                priority=priority,\n                force=force,\n                archive_file=download_file,\n            )\n        except OSError as exc:\n            raise ExtensionError(","sourceCodeStart":181,"sourceCodeEnd":217,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/extensions/_commands.py#L181-L217","documentation":"detect_archive_format could not identify the downloaded bytes as ZIP or tar.gz/tgz, so the installer refuses to proceed and explains the most common cause: the URL returned an HTML login/error page instead of an archive. The message includes the byte count as a hint (HTML pages are typically a few KB).","triggerScenarios":"`specify extension install <url>` where the server returns 200 with HTML/text: unauthenticated private GitHub/GitLab repo redirecting to a login page, a pre-signed URL error in XML/JSON, a soft-404 page, or pointing at a regular file that isn't an archive (e.g. a .py or plain README).","commonSituations":"Private repo release assets without credentials; expired tokens rendered as an HTML error; copy-pasting a blob page URL instead of the raw asset; web servers serving directory listings.","solutions":["Inspect what actually came back: `curl -sL <url> | head -c 300` — HTML confirms the auth/redirect theory","Use the direct raw/download asset URL (e.g. .../releases/download/<tag>/<file> or raw.githubusercontent.com) rather than a UI page","Provide valid credentials to the fetch environment or make the artifact public","Download manually with auth, then `specify extension install ./my-ext.zip`"],"exampleFix":"# before: blob page URL, returns HTML\nspecify extension install https://github.com/org/repo/blob/main/ext.zip\n\n# after: actual asset\nspecify extension install https://github.com/org/repo/releases/download/v1.0.0/ext.zip","handlingStrategy":"validation","validationCode":"import urllib.request\n\ndef url_serves_archive(url: str) -> bool:\n    with urllib.request.urlopen(url, timeout=10) as r:\n        ctype = (r.headers.get('Content-Type') or '').lower()\n        head = r.read(4)\n    return (\n        head[:2] == b'PK'                      # ZIP magic\n        or head[:2] == b'\\x1f\\x8b'              # gzip magic\n        or 'octet-stream' in ctype or 'zip' in ctype or 'tar' in ctype\n    ) and 'text/html' not in ctype","typeGuard":null,"tryCatchPattern":"from specify_cli.extensions import ExtensionError\n\ntry:\n    install_from_url_cmd(project_root, url, speckit_version)\nexcept ExtensionError as e:\n    if 'did not return a ZIP archive' in str(e):\n        # response was HTML: fix auth or use the raw asset URL, then retry","preventionTips":["Verify the URL serves bytes (PK/gzip magic), not a page, before scripted installs","Use direct asset/raw URLs; keep credentials valid for private artifacts"],"tags":["extensions","download","authentication","archive-format"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}