{"record":{"id":"cded2df0a8758ea4","repo":"gitbutlerapp/gitbutler","slug":"thread-panicked-e","errorCode":null,"errorMessage":"thread panicked: {e:?}","messagePattern":"thread panicked: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/gitbutler-user/src/api.rs","lineNumber":337,"sourceCode":"    })\n}\n\n/// Execute an async future on a dedicated thread with its own Tokio runtime.\n///\n/// This keeps the crate's public API synchronous while still using async HTTP\n/// internally, following the same pattern as `but-forge`.\nfn run_async<F, T>(future: F) -> Result<T>\nwhere\n    F: std::future::Future<Output = Result<T>> + Send + 'static,\n    T: Send + 'static,\n{\n    std::thread::spawn(move || {\n        tokio::runtime::Runtime::new()\n            .expect(\"failed to create tokio runtime\")\n            .block_on(future)\n    })\n    .join()\n    .map_err(|e| anyhow::anyhow!(\"thread panicked: {e:?}\"))?\n}\n\n#[cfg(test)]\nmod tests {\n    use super::api_url_override_from_env;\n\n    #[test]\n    fn prefers_backend_specific_override() {\n        let url = api_url_override_from_env(|key| match key {\n            \"GITBUTLER_API_URL\" => Some(\"https://backend.example.com\".to_string()),\n            \"PUBLIC_API_BASE_URL\" => Some(\"https://frontend.example.com\".to_string()),\n            _ => None,\n        });\n\n        assert_eq!(url.as_deref(), Some(\"https://backend.example.com\"));\n    }\n\n    #[test]","sourceCodeStart":319,"sourceCodeEnd":355,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/gitbutler-user/src/api.rs#L319-L355","documentation":"run_async() in gitbutler-user spawns a dedicated OS thread running a fresh tokio runtime to block on an async future. If that thread panics (JoinHandle::join returns Err), the panic payload is reformatted into this anyhow error. It wraps any panic inside the async user-API futures (login token, user fetch/profile, upload).","triggerScenarios":"A panic anywhere inside fetch_login_token, fetch_user_by_token, fetch_user_profile, update_user_profile, or upload_file futures — e.g. explicit panic!/unwrap/expect on bad data, or runtime creation failing in the spawned thread.","commonSituations":"Malformed API responses hitting an unwrap; poisoned state in the future; out-of-memory when constructing the tokio runtime; bugs surfaced during token refresh.","solutions":["Inspect the attached panic payload ({e:?}) to find the panicking callsite and fix the underlying cause.","Check the inner unwrap/expect in the user API path that the payload points to.","Retry the operation after ruling out transient data issues.","Report a bug with the panic payload if it comes from library code."],"exampleFix":"// before\nlet user = fetch_user_by_token(token).unwrap();   // panic propagates as \"thread panicked\"\n// after\nmatch fetch_user_by_token(token) {\n    Ok(user) => /* ... */,\n    Err(e) => log::error!(\"user fetch failed: {e:#}\"),\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match run_async(fetch_user_by_token(token)) {\n    Ok(v) => v,\n    Err(e) if e.to_string().starts_with(\"thread panicked\") => {\n        log::error!(\"panic in user API: {e:#}\");\n        Err(e)\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Avoid unwrap/expect inside async user-API futures; return Results instead.","Keep panic payloads in logs for diagnosis.","Fix underlying causes indicated by the panic payload rather than retrying blindly."],"tags":["panic","tokio","threading","api"],"backgroundTag":"thread-interrupted","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"}