{"record":{"id":"2402484924575755","repo":"gitbutlerapp/gitbutler","slug":"extracted-app-bundle-does-not-contain-expected-dir","errorCode":null,"errorMessage":"Extracted app bundle does not contain expected directory structure (Contents/MacOS)","messagePattern":"Extracted app bundle does not contain expected directory structure \\(Contents/MacOS\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-installer/src/install_macos.rs","lineNumber":339,"sourceCode":"\n    // Find the extracted .app bundle\n    let mut app_dir = None;\n    for entry in fs::read_dir(dest_dir)? {\n        let entry = entry?;\n        let path = entry.path();\n        if path.is_dir() && path.extension().and_then(|s| s.to_str()) == Some(\"app\") {\n            app_dir = Some(path);\n            break;\n        }\n    }\n\n    app_dir.ok_or_else(|| anyhow!(\"No .app bundle found in extracted archive\"))\n}\n\npub(crate) fn verify_app_structure(app_dir: &Path) -> Result<()> {\n    let binaries_dir = app_dir.join(\"Contents/MacOS\");\n    if !binaries_dir.is_dir() {\n        bail!(\n            \"Extracted app bundle does not contain expected directory structure (Contents/MacOS)\"\n        );\n    }\n\n    let required_binaries = [\"gitbutler-git-askpass\", \"gitbutler-tauri\"];\n\n    for binary in &required_binaries {\n        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","sourceCodeStart":321,"sourceCodeEnd":357,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-installer/src/install_macos.rs#L321-L357","documentation":"verify_app_structure() checks the extracted .app bundle on macOS: it requires a Contents/MacOS directory inside the bundle before looking for the gitbutler-git-askpass and gitbutler-tauri binaries. The bail fires when the top-level .app found in the extracted archive lacks Contents/MacOS - i.e. the tarball's layout is not a standard macOS app bundle. Signature verification happens before extraction, so a signed-but-mispackaged or badly extracted archive is the usual cause.","triggerScenarios":"Extraction was truncated (disk full mid-tar), so the bundle directory is incomplete; the signed artifact itself has a non-standard layout (upstream packaging change/bug in a nightly); the archive contains a differently-nested bundle than find_app_bundle() expects.","commonSituations":"Disk filling up during extraction on small volumes; nightly packaging regressions; platform-entry mixups delivering a non-app archive that still passed signature validation.","solutions":["Check free space (df -h) - extraction needs roughly the archive size again - then retry the install","Inspect the artifact manually: curl -L <platform url> | tar -tzf - | head to see the real bundle layout","If the layout is wrong from every host, the release is mispackaged upstream - switch version/channel and report it"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// After manual download, sanity-check the archive layout before installing\nlet out = std::process::Command::new(\"tar\")\n    .args([\"-tzf\", &archive_path])\n    .output()?;\nlet listing = String::from_utf8_lossy(&out.stdout);\nanyhow::ensure!(\n    listing.lines().any(|l| l.contains(\"Contents/MacOS/\")),\n    \"archive lacks the expected macOS bundle layout\"\n);","typeGuard":null,"tryCatchPattern":"match but_installer::run_installation_with_version(request.clone(), false) {\n    Err(e) if e.to_string().contains(\"does not contain expected directory structure\") => {\n        // most cases are truncated extraction (disk full) - free space and retry once\n        std::thread::sleep(std::time::Duration::from_secs(2));\n        but_installer::run_installation_with_version(request, false)\n    }\n    result => result,\n}","preventionTips":["Free disk space before installs - extraction needs about the archive size again","Validate tarball layout in CI before mass deployment","Treat nightly archives with suspicion; pin the release channel for automation"],"tags":["rust","macos","app-bundle","archive-extraction","disk-full","but-installer"],"backgroundTag":"archive-validation-failed","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}