gitbutlerapp/gitbutler · error
Failed to join thread
Error message
Failed to join thread: {e:?} What it means
In list_recently_settled_reviews (GitHub branch), the worker thread's JoinHandle::join() returned Err, meaning the thread panicked (e.g. the inner Runtime::new().unwrap() or a panic in but_github::pr::list_recently_closed). The panic is rewrapped as this anyhow error.
Solutions
- Inspect the Debug payload for the original panic message and location
- Replace unwrap() on Runtime::new() with a mapped error so join can propagate a real error
- Fix the underlying panic in the forge client (e.g. handle unexpected API responses without panicking)
- Avoid the thread+block_on pattern to remove the join/panic indirection
Example fix
// before
Runtime::new().unwrap().block_on(...)
// after
Runtime::new().map_err(|e| anyhow::anyhow!("runtime: {e}"))?.block_on(...) Defensive patterns
Strategy: try-catch
Try / catch
match list_recently_settled_reviews(...) {
Ok(reviews) => reviews,
Err(e) if e.to_string().contains("Failed to join thread") => {
tracing::warn!("review sweep worker panicked: {e:#}");
Vec::new() // degrade gracefully
}
Err(e) => return Err(e),
} Prevention
- Remove unwrap() calls from spawned workers
- Keep workers returning Result so panics never poison the join
- Harden forge clients against unexpected API payloads
When it happens
Trigger: The spawned GitHub PR-sweep thread panics before returning — unwrap failure on Runtime::new(), a panic inside the GitHub client, or poisoned-thread conditions after another panic.
Common situations: Resource exhaustion making runtime creation unwrap-fail; GitHub API returning an unexpected response shape that trips an unwrap; bugs in the client crate.
Related errors
- a committed transaction always materializes a workspace
- anchor is always present in the order at this point
- Archive progress counter thread panicked
- AtSegment anchor always has oldest commit resolved
- broker already configured
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/350b69332a4910b9.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-forge/src/review.rs:683
let repo = repo.clone();
let storage = storage.clone();
let pulls = std::thread::spawn(move || {
tokio::runtime::Runtime::new()
.map_err(|e| {
anyhow::anyhow!(
"Failed to create a runtime for the settled-review sweep: {e}"
)
})?
.block_on(but_github::pr::list_recently_closed(
preferred_account.as_ref(),
&owner,
&repo,
&storage,
))
})
.join()
.map_err(|e| anyhow::anyhow!("Failed to join thread: {e:?}"))??;
pulls.into_iter().map(ForgeReview::from).collect()
}
ForgeName::GitLab => {
let preferred_account = preferred_forge_user
.as_ref()
.and_then(|user| user.gitlab().cloned());
let project_id = GitLabProjectId::new(owner, repo);
let storage = storage.clone();
let mrs = std::thread::spawn(move || {
tokio::runtime::Runtime::new()
.map_err(|e| {
anyhow::anyhow!(
"Failed to create a runtime for the settled-review sweep: {e}"
)
})?
.block_on(but_gitlab::mr::list_recently_closed(View on GitHub (pinned to 58e5313667)