{"record":{"id":"d2594cb133c3e7ce","repo":"gitbutlerapp/gitbutler","slug":"request-failed-e","errorCode":null,"errorMessage":"Request failed: {e}","messagePattern":"Request failed: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"crates/but-update/src/check.rs","lineNumber":125,"sourceCode":"        .default_headers(headers)\n        .timeout(REQUEST_TIMEOUT)\n        .build()?;\n\n    let url = url_override.unwrap_or(UPDATES_CHECK_URL).to_string();\n\n    let result = std::thread::spawn(move || -> anyhow::Result<CheckUpdateStatus> {\n        let runtime = tokio::runtime::Builder::new_current_thread()\n            .enable_all()\n            .build()\n            .map_err(|e| anyhow::anyhow!(\"Failed to create runtime: {e}\"))?;\n\n        runtime.block_on(async {\n            let response = client\n                .post(url)\n                .json(&request_body)\n                .send()\n                .await\n                .map_err(|e| anyhow::anyhow!(\"Request failed: {e}\"))?\n                .error_for_status()\n                .map_err(|e| anyhow::anyhow!(\"Server returned error: {e}\"))?;\n\n            let update_info = response\n                .json::<CheckUpdateStatus>()\n                .await\n                .map_err(|e| anyhow::anyhow!(\"Failed to parse response: {e}\"))?;\n\n            Ok(update_info)\n        })\n    })\n    .join()\n    .map_err(|_| anyhow::anyhow!(\"Update check thread panicked\"))?;\n\n    // Save to cache (convert to but_db types)\n    if let Ok(status) = &result {\n        let now = chrono::Utc::now();\n","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-update/src/check.rs#L107-L143","documentation":"The reqwest POST to the update server (https://app.gitbutler.com/updates by default) failed at the transport layer — DNS resolution, TCP connect, TLS handshake, or the 30-second timeout fired. The error string chains the underlying reqwest error with the specific cause.","triggerScenarios":"check_status() with no network connectivity, a proxy/firewall blocking app.gitbutler.com, DNS failure, or a response slower than the REQUEST_TIMEOUT of 30s.","commonSituations":"Offline or captive-portal networks; corporate proxies that MITM TLS; transient outages of the update endpoint; CI runners without egress.","solutions":["Verify connectivity to https://app.gitbutler.com/updates (curl -I) from the same machine","Configure proxy env vars (HTTPS_PROXY) if a corporate proxy is required","Treat as non-fatal: update checks are optional; skip/retry later when back online"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// preflight connectivity before the check\nif !std::net::TcpStream::connect_timeout(&\"app.gitbutler.com:443\".to_socket_addrs()?.next().unwrap(), std::time::Duration::from_secs(3)).is_ok() {\n    return Ok(None); // offline: use cached update info instead\n}","typeGuard":null,"tryCatchPattern":"let mut attempt = 0;\nloop {\n    match check_status(app, settings, cache) {\n        Err(e) if e.to_string().contains(\"Request failed\") && attempt < 2 => { attempt += 1; std::thread::sleep(backoff(attempt)); }\n        other => break other,\n    }\n}","preventionTips":["Honor offline state: skip checks when the OS reports no connectivity","Configure HTTPS_PROXY in proxied environments before reporting bugs","Keep update-check intervals sparse; every failure is already retried on the next app start"],"tags":["rust","network","http","timeout","update-checker","but-update"],"backgroundTag":"network-request-failed","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}