{"record":{"id":"98c6d9e1577389ba","repo":"gitbutlerapp/gitbutler","slug":"failed-to-construct-but-cli-url","errorCode":null,"errorMessage":"Failed to construct but cli URL","messagePattern":"Failed to construct but cli URL","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-installer/src/install_linux.rs","lineNumber":35,"sourceCode":"    platform_info: &PlatformInfo,\n    release: &Release,\n    channel: Option<Channel>,\n) -> Result<()> {\n    let appimage_download_url = platform_info.url.as_deref().ok_or_else(|| {\n        anyhow::anyhow!(\n            \"No download URL for platform {} in release {}\",\n            config.platform,\n            release.version\n        )\n    })?;\n\n    // Note: For now we find the CLI and signature by convention on Linux, but we should update the\n    // API (or create a new one) to contain this information.\n    let filename = \"but\";\n    let base_download_url = appimage_download_url\n        .rsplit_once('/')\n        .map(|(base, _)| base.to_string())\n        .ok_or_else(|| anyhow::anyhow!(\"Failed to construct but cli URL\"))?;\n    let download_url = format!(\"{base_download_url}/{filename}\");\n    let signature_url = format!(\"{download_url}.sig\");\n\n    validate_download_url(&signature_url)?;\n    validate_download_url(&download_url)?;\n    info(&format!(\"Download URL: {download_url}\"));\n\n    let temp_dir = tempfile::Builder::new()\n        .prefix(\"gitbutler-install.\")\n        .tempdir()?;\n    let tmp_filepath = temp_dir.path().join(filename);\n\n    info(&format!(\"Downloading GitButler {}...\", release.version));\n    download_file(&download_url, &tmp_filepath)?;\n    info(\"Download completed successfully\");\n\n    let signature_b64 = download_to_string(&signature_url).with_context(|| {\n        anyhow!(\"Failed to get signature for but, requested version may be too old\")","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-installer/src/install_linux.rs#L17-L53","documentation":"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.","triggerScenarios":"download_and_install_app on Linux when platform_info.url is a bare filename or otherwise slash-less string, making rsplit_once('/') return None.","commonSituations":"A malformed release asset URL stored on the server (e.g. a relative path without slashes); corrupted release metadata from a broken publisher pipeline.","solutions":["Check the release metadata: platform_info.url should be a full https URL with slashes","Report/fix the release publishing pipeline if the URL is malformed server-side","As a caller, validate the URL shape before invoking the installer"],"exampleFix":"// before\nlet base = url.rsplit_once('/').map(|(b, _)| b.to_string()).ok_or_else(...)?;\n// after\nanyhow::ensure!(url.contains(\"/\"), \"invalid asset url: {url}\");\nlet base = url.rsplit_once('/').unwrap().0.to_string();","handlingStrategy":"validation","validationCode":"fn plausible_asset_url(u: &str) -> bool {\n    u.starts_with(\"https://\") && u.contains('/') && u.rsplit('/').next().map_or(false, |f| !f.is_empty())\n}","typeGuard":null,"tryCatchPattern":"if let Err(e) = download_and_install_app(...) {\n    if e.to_string().contains(\"Failed to construct but cli URL\") {\n        eprintln!(\"Release metadata has a malformed asset URL — report to publishers\");\n    }\n    return Err(e.into());\n}","preventionTips":["Validate asset URLs (scheme + path) before install","Treat slash-less URLs as broken release metadata and skip that release","Add a publishing-pipeline check that every asset URL is absolute"],"tags":["url","linux","release-metadata","installer"],"backgroundTag":"invalid-url-format","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}