{"record":{"id":"5b799627ad64bfe4","repo":"gitbutlerapp/gitbutler","slug":"no-download-url-for-platform-in-release","errorCode":null,"errorMessage":"No download URL for platform {} in release {}","messagePattern":"No download URL for platform (.+?) in release (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/but-installer/src/install_linux.rs","lineNumber":22,"sourceCode":"\nuse anyhow::{Context, Result, anyhow, bail};\n\nuse crate::{\n    config::{Channel, InstallerConfig},\n    download::{download_file, download_to_string},\n    install::{but_binary_path, validate_installed_binary, verify_signature},\n    release::{PlatformInfo, Release, validate_download_url},\n    ui::{info, warn},\n};\n\npub(crate) fn download_and_install_app(\n    config: &InstallerConfig,\n    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)?;","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-installer/src/install_linux.rs#L4-L40","documentation":"Thrown by download_and_install_app in crates/but-installer/src/install_linux.rs when the PlatformInfo resolved for this system has url == None. The release metadata returned by the GitButler API (release.platforms keyed by config.platform) contains an entry for the platform, but that entry carries no AppImage download URL, so there is nothing to download and install.","triggerScenarios":"Calling the Linux installer with a release whose platforms entry for config.platform (e.g. \"linux\") exists but has a null/missing url field; requesting an old release version that predates AppImage publishing; an API response shape change that stops populating PlatformInfo.url.","commonSituations":"Installing a pinned old version (VersionRequest::Specific) whose artifacts were incomplete; running on an architecture/variant the release pipeline did not publish an AppImage for; the releases API returning a partially-filled platform entry.","solutions":["Install the latest release instead of the pinned version (the latest release always carries a Linux AppImage URL).","Inspect the release JSON from the API and confirm the platform key (config.platform) maps to an entry with a non-null url; adjust platform detection if the key mismatches.","Try the nightly channel, which publishes fresh artifacts including the Linux AppImage.","If the API genuinely lacks the artifact for that version, report it to GitButler — nothing client-side can fabricate the URL."],"exampleFix":"// before: assume the platform entry has a URL\nlet url = platform_info.url.as_deref().ok_or_else(|| {\n    anyhow::anyhow!(\"No download URL for platform {} in release {}\", config.platform, release.version)\n})?;\n\n// after (installer caller): pre-check and pick a newer release\nif platform_info.url.is_none() {\n    warn(&format!(\n        \"Release {} has no Linux artifact; falling back to latest\",\n        release.version\n    ));\n    let release = fetch_release(&VersionRequest::Release)?;\n    // re-resolve platform_info from the new release before installing\n}","handlingStrategy":"validation","validationCode":"// Before calling download_and_install_app on Linux\nuse but_installer::release::PlatformInfo;\n\nfn linux_artifact_available(platform_info: &PlatformInfo) -> bool {\n    platform_info.url.is_some()\n}\n\nif !linux_artifact_available(platform_info) {\n    eprintln!(\"release {} has no Linux AppImage; try latest\", release.version);\n    return Ok(());\n}","typeGuard":"fn has_download_url(p: &PlatformInfo) -> bool {\n    p.url.as_deref().map(|u| !u.is_empty()).unwrap_or(false)\n}","tryCatchPattern":"match download_and_install_app(&config, platform_info, &release, channel) {\n    Ok(()) => {},\n    Err(e) if e.to_string().contains(\"No download URL for platform\") => {\n        // fall back to the latest release and retry once\n    },\n    Err(e) => return Err(e),\n}","preventionTips":["Prefer VersionRequest::Release (latest) for automated installs; pinned old versions are the main source of missing AppImage URLs.","Validate release.platforms[platform].url is non-null before starting the download.","Log release.version whenever an install fails so missing-artifact reports are actionable."],"tags":["installer","linux","appimage","release-metadata","download-url"],"backgroundTag":"missing-platform-artifact","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}