{"record":{"id":"5f7c26a663556a78","repo":"gitbutlerapp/gitbutler","slug":"no-panic","errorCode":null,"errorMessage":"no panic","messagePattern":"no panic","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/gitbutler-branch-actions/src/branch.rs","lineNumber":870,"sourceCode":"                    change_tx.send(change.detach()).ok();\n                    Ok(std::ops::ControlFlow::Continue(()))\n                })?;\n            let (number_of_files, lines_added, lines_removed) = rex_rx.recv()?;\n\n            let branch_data = BranchListingDetails {\n                name: branch.name,\n                lines_added,\n                lines_removed,\n                number_of_files,\n                authors: authors.into_iter().collect(),\n                number_of_commits: num_commits,\n                stack: branch.stack,\n            };\n            enriched_branches.push(branch_data);\n        }\n        (diffstats, merge_bases)\n    };\n    diffstats.join().expect(\"no panic\")?;\n    merge_bases.join().expect(\"no panic\")?;\n    Ok(enriched_branches)\n}\n\n/// Represents a fat struct with all the data associated with a branch\n#[derive(Debug, Clone, Serialize, PartialEq)]\n#[cfg_attr(feature = \"export-schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\")]\npub struct BranchListingDetails {\n    /// The name of the branch (e.g. `main`, `feature/branch`), excluding the remote name\n    #[cfg_attr(feature = \"export-schema\", schemars(with = \"String\"))]\n    pub name: BranchIdentity,\n    /// The number of lines added within the branch\n    /// Since the virtual branch, local branch and the remote one can have different number of lines removed,\n    /// the value from the virtual branch (if present) takes the highest precedence,\n    /// followed by the local branch and then the remote branches (taking the max if there are multiple).\n    /// If this branch has a virtual branch, lines_added does NOT include the uncommitted lines.\n    pub lines_added: usize,","sourceCodeStart":852,"sourceCodeEnd":888,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/gitbutler-branch-actions/src/branch.rs#L852-L888","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\ndiffstats.join().expect(\"no panic\")?;\nmerge_bases.join().expect(\"no panic\")?;\n// after\ndiffstats\n    .join()\n    .map_err(|e| anyhow::anyhow!(\"diffstats worker panicked: {e:?}\"))??;\nmerge_bases\n    .join()\n    .map_err(|e| anyhow::anyhow!(\"merge_bases worker panicked: {e:?}\"))??;","handlingStrategy":"try-catch","validationCode":"// Pre-validate repo state before heavy listing work:\nrepo.object_database()?.refresh()?; // surfaces corruption as an error, not a thread panic","typeGuard":"fn join_ok<T>(h: std::thread::JoinHandle<T>) -> Option<T> {\n    h.ok()\n}","tryCatchPattern":"// Map a worker panic to a normal error instead of re-panicking:\nmatch diffstats.join() {\n    Ok(result) => result?,\n    Err(panic_payload) => return Err(anyhow::anyhow!(\"diffstats thread panicked: {panic_payload:?}\")),\n}","preventionTips":["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"],"tags":["rust","panic","threads","join","error-propagation"],"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"}