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 emptyView on GitHub (pinned to caf1f223d3)
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
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
- 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
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
- Failed to install new version: {}. Also failed to restore ba
- Failed to move new installation into place: {e}
- New installation verification failed - 'but' binary cannot r
- Failed to install new version: {e}. Previous installation re
- Failed to create symlink and failed to remove new installati
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/2402484924575755.
Report an issue: GitHub.