gitbutlerapp/gitbutler · error

Extracted app bundle does not contain expected directory str

Error message

Extracted app bundle does not contain expected directory structure (Contents/MacOS)

What it means

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.

Source

Thrown at crates/but-installer/src/install_macos.rs:339

    // Find the extracted .app bundle
    let mut app_dir = None;
    for entry in fs::read_dir(dest_dir)? {
        let entry = entry?;
        let path = entry.path();
        if path.is_dir() && path.extension().and_then(|s| s.to_str()) == Some("app") {
            app_dir = Some(path);
            break;
        }
    }

    app_dir.ok_or_else(|| anyhow!("No .app bundle found in extracted archive"))
}

pub(crate) fn verify_app_structure(app_dir: &Path) -> Result<()> {
    let binaries_dir = app_dir.join("Contents/MacOS");
    if !binaries_dir.is_dir() {
        bail!(
            "Extracted app bundle does not contain expected directory structure (Contents/MacOS)"
        );
    }

    let required_binaries = ["gitbutler-git-askpass", "gitbutler-tauri"];

    for binary in &required_binaries {
        let binary_path = binaries_dir.join(binary);
        if !binary_path.exists() {
            bail!("Missing required binary: {binary}");
        }
    }

    Ok(())
}

fn validate_tarball(path: &Path) -> Result<()> {
    // Check if file is not empty

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Check free space (df -h) - extraction needs roughly the archive size again - then retry the install
  2. Inspect the artifact manually: curl -L <platform url> | tar -tzf - | head to see the real bundle layout
  3. If the layout is wrong from every host, the release is mispackaged upstream - switch version/channel and report it
Defensive patterns

Strategy: retry

Validate before calling

// After manual download, sanity-check the archive layout before installing
let out = std::process::Command::new("tar")
    .args(["-tzf", &archive_path])
    .output()?;
let listing = String::from_utf8_lossy(&out.stdout);
anyhow::ensure!(
    listing.lines().any(|l| l.contains("Contents/MacOS/")),
    "archive lacks the expected macOS bundle layout"
);

Try / catch

match but_installer::run_installation_with_version(request.clone(), false) {
    Err(e) if e.to_string().contains("does not contain expected directory structure") => {
        // most cases are truncated extraction (disk full) - free space and retry once
        std::thread::sleep(std::time::Duration::from_secs(2));
        but_installer::run_installation_with_version(request, false)
    }
    result => result,
}

Prevention

When it happens

Trigger: 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.

Common situations: Disk filling up during extraction on small volumes; nightly packaging regressions; platform-entry mixups delivering a non-app archive that still passed signature validation.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/2402484924575755. Report an issue: GitHub.