gitbutlerapp/gitbutler · error
no panic
Error message
no panic
What it means
get_branch_listing_details computes diffstats and merge bases on spawned worker threads (JoinHandle::join) and asserts the join itself cannot fail with `.expect("no panic")`. A join returns Err only if the worker thread panicked; this expect converts a worker panic into a panic on the caller thread. The subsequent `?` still propagates the computation's actual Result errors.
Solutions
- Inspect the underlying thread panic: join().unwrap_or_else(|e| e.downcast/payload) and surface the real message
- Replace expect with proper error propagation: `diffstats.join().map_err(|e| anyhow!("worker panicked: {e:?}"))??`
- Fix the root panic inside the worker closures so they return Results instead of panicking
Example fix
// before
diffstats.join().expect("no panic")?;
merge_bases.join().expect("no panic")?;
// after
diffstats
.join()
.map_err(|e| anyhow::anyhow!("diffstats worker panicked: {e:?}"))??;
merge_bases
.join()
.map_err(|e| anyhow::anyhow!("merge_bases worker panicked: {e:?}"))??; Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-validate repo state before heavy listing work: repo.object_database()?.refresh()?; // surfaces corruption as an error, not a thread panic
Type guard
fn join_ok<T>(h: std::thread::JoinHandle<T>) -> Option<T> {
h.ok()
} Try / catch
// Map a worker panic to a normal error instead of re-panicking:
match diffstats.join() {
Ok(result) => result?,
Err(panic_payload) => return Err(anyhow::anyhow!("diffstats thread panicked: {panic_payload:?}")),
} Prevention
- Keep worker closures panic-free: return Result from all fallible operations inside them
- Never trust join() with expect/unwrap — always map the Err payload to a descriptive error
- Run `git fsck` on repositories that produce unexpected panics during branch listing
When it happens
Trigger: Calling get_branch_listing_details when one of the spawned closure threads panics (e.g. an expect inside diffstat computation or merge-base resolution), making join() return Err.
Common situations: A repo state (corrupt object, unusual ref, huge diff) causes a panic inside the thread computing diffstats or merge bases; the surfaced error is 'no panic' rather than the underlying panic message.
Related errors
- a committed transaction always materializes a workspace
- anchor is always present in the order at this point
- AtSegment anchor always has oldest commit resolved
- broker already configured
- broker has not been configured
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/5f7c26a663556a78.
Report an issue: GitHub.
Appendix: source
Thrown at crates/gitbutler-branch-actions/src/branch.rs:870
change_tx.send(change.detach()).ok();
Ok(std::ops::ControlFlow::Continue(()))
})?;
let (number_of_files, lines_added, lines_removed) = rex_rx.recv()?;
let branch_data = BranchListingDetails {
name: branch.name,
lines_added,
lines_removed,
number_of_files,
authors: authors.into_iter().collect(),
number_of_commits: num_commits,
stack: branch.stack,
};
enriched_branches.push(branch_data);
}
(diffstats, merge_bases)
};
diffstats.join().expect("no panic")?;
merge_bases.join().expect("no panic")?;
Ok(enriched_branches)
}
/// Represents a fat struct with all the data associated with a branch
#[derive(Debug, Clone, Serialize, PartialEq)]
#[cfg_attr(feature = "export-schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
pub struct BranchListingDetails {
/// The name of the branch (e.g. `main`, `feature/branch`), excluding the remote name
#[cfg_attr(feature = "export-schema", schemars(with = "String"))]
pub name: BranchIdentity,
/// The number of lines added within the branch
/// Since the virtual branch, local branch and the remote one can have different number of lines removed,
/// the value from the virtual branch (if present) takes the highest precedence,
/// followed by the local branch and then the remote branches (taking the max if there are multiple).
/// If this branch has a virtual branch, lines_added does NOT include the uncommitted lines.
pub lines_added: usize,View on GitHub (pinned to 58e5313667)