{"record":{"id":"cb747e0bb61d4f01","repo":"gitbutlerapp/gitbutler","slug":"downloaded-file-does-not-appear-to-be-a-valid-gzip-archive","errorCode":null,"errorMessage":"Downloaded file does not appear to be a valid gzip archive","messagePattern":"Downloaded file does not appear to be a valid gzip archive","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-installer/src/install_macos.rs","lineNumber":365,"sourceCode":"        let binary_path = binaries_dir.join(binary);\n        if !binary_path.exists() {\n            bail!(\"Missing required binary: {binary}\");\n        }\n    }\n\n    Ok(())\n}\n\nfn validate_tarball(path: &Path) -> Result<()> {\n    // Check if file is not empty\n    let metadata = fs::metadata(path).context(\"Failed to read tarball metadata\")?;\n    if metadata.len() == 0 {\n        bail!(\"Downloaded file is empty\");\n    }\n\n    // Check for gzip magic bytes (1f 8b)\n    if !is_gzip_file(path)? {\n        bail!(\"Downloaded file does not appear to be a valid gzip archive\");\n    }\n\n    Ok(())\n}\n\n/// Check if a file has valid gzip magic bytes\nfn is_gzip_file(path: &Path) -> Result<bool> {\n    let mut file = File::open(path).context(\"Failed to open file\")?;\n    let mut magic_bytes = [0u8; 2];\n\n    // If we can't read 2 bytes, it's definitely not a valid gzip file\n    match file.read_exact(&mut magic_bytes) {\n        Ok(_) => Ok(magic_bytes == [0x1f, 0x8b]),\n        Err(_) => Ok(false),\n    }\n}\n\n/// Recursively remove the macOS quarantine attribute from a directory","sourceCodeStart":347,"sourceCodeEnd":383,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-installer/src/install_macos.rs#L347-L383","documentation":"After the emptiness check, validate_tarball reads the file's first bytes and requires the gzip magic bytes 1f 8b. This error means the downloaded file exists but is not a gzip archive, so extraction would fail and the installer stops here instead.","triggerScenarios":"The downloaded asset is not gzip-compressed: HTML/error page saved instead of the tarball, plain tar (uncompressed), zip, or binary corruption altering the first two bytes.","commonSituations":"Wrong asset URL (e.g. API returns an error page with 200), CDN serving an error/captcha page, release asset replaced with uncompressed tar, redirect to a login page.","solutions":["Check the downloaded file with `file` / `xxd path | head` to see what it actually is","Verify the download URL points at the correct .tar.gz asset and not an HTML error page","Delete the file and re-download; check for redirects to untrusted/intermediate pages","Confirm the release asset on the server is a valid gzip archive"],"exampleFix":"// before\nlet resp = client.get(url).send()?;\nfs::write(path, resp.bytes()?);\n// after\nlet resp = client.get(url).send()?;\nanyhow::ensure!(resp.status() == 200, \"HTTP {} for {url}\", resp.status());\nlet ct = resp.headers().get(\"content-type\").and_then(|v| v.to_str().ok()).unwrap_or(\"\");\nanyhow::ensure!(!ct.contains(\"text/html\"), \"unexpected content-type {ct}\");\nfs::write(path, resp.bytes()?)?;","handlingStrategy":"validation","validationCode":"fn has_gzip_magic(path: &std::path::Path) -> std::io::Result<bool> {\n    use std::io::Read;\n    let mut f = std::fs::File::open(path)?;\n    let mut b = [0u8; 2];\n    f.read_exact(&mut b)?;\n    Ok(b == [0x1f, 0x8b])\n}","typeGuard":null,"tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"valid gzip archive\") => {\n        // inspect file content, re-download from correct asset URL\n    }\n    other => other?,\n}","preventionTips":["Validate Content-Type is application/gzip or application/x-tar+gzip","Never save a 200 HTML error page as the asset","Use checksums published alongside the release","Verify magic bytes before extraction in your own pipeline"],"tags":["download","gzip","validation","install","macos"],"backgroundTag":"invalid-argument-format","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}