{"record":{"id":"87806b615dfe9edd","repo":"gitbutlerapp/gitbutler","slug":"prefiltered","errorCode":null,"errorMessage":"prefiltered","messagePattern":"prefiltered","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-graph/src/init/post.rs","lineNumber":556,"sourceCode":"            .inner\n            .node_indices()\n            .filter(|sidx| {\n                self[*sidx]\n                    .commits\n                    .first()\n                    .is_some_and(|c| !c.refs.is_empty())\n            })\n            .collect();\n        for sidx in segments_with_refs_on_first_commit {\n            let s = &mut self.inner[sidx];\n            let first_commit = &mut s.commits[0];\n            if let Some(srn) = &s.ref_info {\n                if let Some(pos) = first_commit.refs.iter().position(|rn| rn == srn) {\n                    first_commit.refs.remove(pos);\n                }\n            } else {\n                match first_commit.refs.len() {\n                    0 => unreachable!(\"prefiltered\"),\n                    1 => {\n                        if first_commit\n                            .refs\n                            .first()\n                            .is_some_and(|rn| rn.ref_name.category() == Some(Category::LocalBranch))\n                        {\n                            s.ref_info = first_commit.refs.pop();\n                            s.metadata = meta\n                                .branch_opt(s.ref_name().expect(\"just set\"))\n                                .ok()\n                                .flatten()\n                                .map(|md| SegmentMetadata::Branch(md.clone()));\n                        }\n                    }\n                    _ => {\n                        if !inserted_proxy_segments.contains(&sidx) {\n                            continue;\n                        }","sourceCodeStart":538,"sourceCodeEnd":574,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-graph/src/init/post.rs#L538-L574","documentation":"Internal invariant panic in but-graph's post-initialization pass. The loop iterates segments collected by prefiltering for a non-empty refs list on the first commit, so the `0 =>` arm of `first_commit.refs.len()` should be unreachable: it only fires if the prefilter and the loop body disagree. This is a bug in but-graph (crates/but-graph/src/init/post.rs), not a caller error.","triggerScenarios":"Running but_graph::init over a repository where a segment that passed the `!c.refs.is_empty()` filter ends up with an empty first-commit refs list by the time the loop body runs - e.g. an earlier pass in the same loop emptied the refs, or refs changed between collection and processing. Any `but_graph` init/`Graph` construction path reaches this code.","commonSituations":"Modifying the init/post.rs passes during development and breaking the prefilter invariant; corrupted or unusual ref layouts (empty ref names, races with a concurrent git process deleting refs while the graph is built); version drift between but-graph passes.","solutions":["Report a bug to GitButler with the repository/fixture that reproduces it - as an internal unreachable, the graph input is needed to fix the pass ordering","Check for concurrent git processes (another but instance, an IDE, git CLI) mutating refs while the graph initializes, and retry once they are idle","Upgrade but-graph / the but binary to a version containing the fix","As a maintainer: make the filter and loop agree, e.g. re-check emptiness inside the loop (`if s.commits[0].refs.is_empty() { continue; }`) instead of asserting"],"exampleFix":"// before (init/post.rs)\nlet segments_with_refs_on_first_commit: Vec<_> = ...filter(...is_some_and(|c| !c.refs.is_empty()))...collect();\nfor sidx in segments_with_refs_on_first_commit {\n    match first_commit.refs.len() {\n        0 => unreachable!(\"prefiltered\"),\n        1 => { /* extract ref_info */ }\n        _ => {}\n    }\n}\n// after - tolerate the prefilter going stale\nfor sidx in segments_with_refs_on_first_commit {\n    let s = &mut self.inner[sidx];\n    if s.commits[0].refs.is_empty() { continue; }\n    match s.commits[0].refs.len() { ... }\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// graph init is an internal-invariant panic; isolate it if it can take down a host app\nuse std::panic::{catch_unwind, AssertUnwindSafe};\nlet graph = catch_unwind(AssertUnwindSafe(|| but_graph::init(&repo).build()))\n    .map_err(|p| anyhow::anyhow!(\"but_graph init panicked (likely bug in init/post.rs): {p:?}\"))?;","preventionTips":["Run but_graph::init when no other process is mutating refs (close IDEs/git CLI during graph builds)","Pin the but-graph version that matches your fixture tests so new passes do not surprise you","When developing passes in init/post.rs, keep the prefilter and the loop-body match arm in one review unit","Capture the repo state (git bundle / fixture) immediately when the panic fires so it can be reported reproducibly"],"tags":["rust","but-graph","panic","invariant","internal-bug","refs"],"backgroundTag":"rust-unreachable-panic","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}