{"record":{"id":"40ce79f3132fb76f","repo":"gitbutlerapp/gitbutler","slug":"failed-to-join-thread-e","errorCode":null,"errorMessage":"Failed to join thread: {e:?}","messagePattern":"Failed to join thread: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/but-forge/src/ci.rs","lineNumber":119,"sourceCode":"            let owner = owner.clone();\n            let repo = repo.clone();\n            let storage = storage.clone();\n            let reference = reference.to_string();\n            let reference_for_checks = reference.clone();\n\n            let checks = std::thread::spawn(move || {\n                tokio::runtime::Runtime::new()\n                    .unwrap()\n                    .block_on(but_github::checks::list_for_ref(\n                        preferred_account.as_ref(),\n                        &owner,\n                        &repo,\n                        &reference,\n                        &storage,\n                    ))\n            })\n            .join()\n            .map_err(|e| anyhow::anyhow!(\"Failed to join thread: {e:?}\"))?;\n            checks.map(|maybe_runs| {\n                maybe_runs.map(|runs| {\n                    runs.into_iter()\n                        .map(|check| {\n                            let mut ci_check = CiCheck::from(check);\n                            ci_check.reference = reference_for_checks.to_string();\n                            ci_check\n                        })\n                        .collect()\n                })\n            })\n        }\n        ForgeName::GitLab => {\n            let preferred_account = preferred_forge_user\n                .as_ref()\n                .and_then(|user| user.gitlab().cloned());\n            let gl = but_gitlab::GitLabClient::from_storage(storage, preferred_account.as_ref())?;\n","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-forge/src/ci.rs#L101-L137","documentation":"To fetch GitHub CI checks synchronously, but-forge spawns a thread that builds a tokio runtime and calls but_github::checks::list_for_ref. join() returned Err, meaning that thread panicked; the error embeds the panic payload's Debug output. Note the thread body uses Runtime::new().unwrap(), so tokio runtime creation failure is one concrete panic source inside that thread.","triggerScenarios":"Any panic inside the GitHub checks thread: Runtime::new().unwrap() failing under resource pressure (thread/fd limits), or a bug in the checks listing or CiCheck mapping code.","commonSituations":"Sandboxed desktop environments or containers with low thread/file-descriptor ulimits; regressions in the GitHub checks response mapping.","solutions":["Inspect the {e:?} payload in the message - it names the panic reason","If it mentions runtime or io creation errors, raise ulimits (threads, fds) for the application process","Otherwise capture the panic output and report it upstream with the forge, owner/repo and ref"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"match list_ci_checks(&forge, owner, repo, reference) {\n    Err(err) if err.to_string().contains(\"Failed to join thread\") => {\n        // render CI status as 'unavailable' and keep the rest of the PR view working\n    }\n    result => result,\n}","preventionTips":["Keep process ulimits (threads, fds) generous in packaged desktop apps","Throttle concurrent CI lookups instead of firing one thread per PR","Log the panic payload verbatim - it is the only clue to the in-thread panic"],"tags":["github","ci-checks","thread-panic","tokio"],"backgroundTag":"worker-thread-panic","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}