gitbutlerapp/gitbutler · error

New installation verification failed - 'but' binary cannot r

Error message

New installation verification failed - 'but' binary cannot run (may be corrupted or blocked by macOS)

What it means

macOS path: before any swap, install_macos stages the extracted .app, creates a candidate 'but' symlink to its gitbutler-tauri binary, and runs 'but --version'. If that fails, the staged new install is deleted and the installer bails - the existing installation is untouched at this point. The parenthetical names the two usual causes on macOS: a corrupted binary, or Gatekeeper blocking an unnotarized/quarantined one.

Source

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

    }

    // Create temporary symlink to test the new installation
    let new_app_macos_dir = install_app_new.join("Contents/MacOS/gitbutler-tauri");
    let _ = fs::remove_file(&but_new);

    unix_fs::symlink(&new_app_macos_dir, &but_new)?;

    // Verify the new installation works
    let verify_status = Command::new(&but_new)
        .arg("--version")
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .status();

    if !verify_status.map(|s| s.success()).unwrap_or(false) {
        fs::remove_dir_all(&install_app_new)?;
        fs::remove_file(&but_new)?;
        bail!(
            "New installation verification failed - 'but' binary cannot run (may be corrupted or blocked by macOS)"
        );
    }

    // New installation is valid - now do the atomic swap
    info("Swapping new installation into place...");

    // Helper to clean up stale installation artifacts
    let cleanup_artifacts = || {
        let _ = fs::remove_dir_all(&install_app_new);
        let _ = fs::remove_file(&but_new);
    };

    // Backup existing installation if it exists
    let had_backup = install_app.exists();
    if had_backup && let Err(e) = fs::rename(&install_app, &install_app_backup) {
        // Failed to backup - clean up artifacts before failing
        cleanup_artifacts();

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Run the extracted binary directly ( staging path or the downloaded .app: open the app once and check Privacy & Security for a block notice, then 'Allow Anyway'
  2. Retry the install to rule out a corrupted download
  3. On arm64 with an x86_64 artifact: softwareupdate --install-rosetta
  4. Prefer the notarized release channel over nightly on locked-down Macs
Defensive patterns

Strategy: try-catch

Validate before calling

// After manual download/extract, verify the binary executes before installing
let status = std::process::Command::new(&extracted_but)
    .arg("--version")
    .stdout(std::process::Stdio::null())
    .stderr(std::process::Stdio::null())
    .status();
anyhow::ensure!(status.is_ok_and(|s| s.success()), "binary blocked or corrupted");

Try / catch

match but_installer::run_installation_with_version(request, false) {
    Err(e) if e.to_string().contains("New installation verification failed") => {
        // check Gatekeeper/quarantine and Rosetta, then retry once
        clear_quarantine_and_retry()?;
    }
    result => result,
}

Prevention

When it happens

Trigger: The staged binary exits non-zero or is killed: Gatekeeper kills an unnotarized nightly (SIGKILL before main), the downloaded bundle is corrupted, an x86_64 artifact runs on arm64 without Rosetta, or the extraction lost the executable bit.

Common situations: Nightly channel on managed Macs; corporate MDM enforcing stricter Gatekeeper; missing Rosetta 2 on Apple Silicon; CDN object corruption that still passed signature check.

Related errors


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