gitbutlerapp/gitbutler · error

Failed to construct but cli URL

Error message

Failed to construct but cli URL

What it means

On Linux the CLI binary and its signature are found 'by convention' by stripping the filename from the AppImage URL. This error fires when rsplit_once('/') fails — i.e. the AppImage URL contains no '/' at all — so the base URL for the `but` CLI cannot be derived.

Solutions

  1. Check the release metadata: platform_info.url should be a full https URL with slashes
  2. Report/fix the release publishing pipeline if the URL is malformed server-side
  3. As a caller, validate the URL shape before invoking the installer

Example fix

// before
let base = url.rsplit_once('/').map(|(b, _)| b.to_string()).ok_or_else(...)?;
// after
anyhow::ensure!(url.contains("/"), "invalid asset url: {url}");
let base = url.rsplit_once('/').unwrap().0.to_string();
Defensive patterns

Strategy: validation

Validate before calling

fn plausible_asset_url(u: &str) -> bool {
    u.starts_with("https://") && u.contains('/') && u.rsplit('/').next().map_or(false, |f| !f.is_empty())
}

Try / catch

if let Err(e) = download_and_install_app(...) {
    if e.to_string().contains("Failed to construct but cli URL") {
        eprintln!("Release metadata has a malformed asset URL — report to publishers");
    }
    return Err(e.into());
}

Prevention

When it happens

Trigger: download_and_install_app on Linux when platform_info.url is a bare filename or otherwise slash-less string, making rsplit_once('/') return None.

Common situations: A malformed release asset URL stored on the server (e.g. a relative path without slashes); corrupted release metadata from a broken publisher pipeline.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/98c6d9e1577389ba. Report an issue: GitHub.

Appendix: source

Thrown at crates/but-installer/src/install_linux.rs:35

    platform_info: &PlatformInfo,
    release: &Release,
    channel: Option<Channel>,
) -> Result<()> {
    let appimage_download_url = platform_info.url.as_deref().ok_or_else(|| {
        anyhow::anyhow!(
            "No download URL for platform {} in release {}",
            config.platform,
            release.version
        )
    })?;

    // Note: For now we find the CLI and signature by convention on Linux, but we should update the
    // API (or create a new one) to contain this information.
    let filename = "but";
    let base_download_url = appimage_download_url
        .rsplit_once('/')
        .map(|(base, _)| base.to_string())
        .ok_or_else(|| anyhow::anyhow!("Failed to construct but cli URL"))?;
    let download_url = format!("{base_download_url}/{filename}");
    let signature_url = format!("{download_url}.sig");

    validate_download_url(&signature_url)?;
    validate_download_url(&download_url)?;
    info(&format!("Download URL: {download_url}"));

    let temp_dir = tempfile::Builder::new()
        .prefix("gitbutler-install.")
        .tempdir()?;
    let tmp_filepath = temp_dir.path().join(filename);

    info(&format!("Downloading GitButler {}...", release.version));
    download_file(&download_url, &tmp_filepath)?;
    info("Download completed successfully");

    let signature_b64 = download_to_string(&signature_url).with_context(|| {
        anyhow!("Failed to get signature for but, requested version may be too old")

View on GitHub (pinned to 58e5313667)