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

  1. Inspect the Debug payload for the original panic message and location
  2. Replace unwrap() on Runtime::new() with a mapped error so join can propagate a real error
  3. Fix the underlying panic in the forge client (e.g. handle unexpected API responses without panicking)
  4. 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

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


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)