{"record":{"id":"eeb95f1c0fe131a4","repo":"gitbutlerapp/gitbutler","slug":"atsegment-anchor-always-has-oldest-commit-resolved","errorCode":null,"errorMessage":"AtSegment anchor always has oldest commit resolved","messagePattern":"AtSegment anchor always has oldest commit resolved","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-transaction/src/lib.rs","lineNumber":671,"sourceCode":"                        InsertSide::Below,\n                    )?;\n                }\n                Some(but_workspace::branch::create_reference::Anchor::AtSegment {\n                    ref_name: anchor_ref,\n                    position: but_workspace::branch::create_reference::Position::Above,\n                }) => {\n                    editor.insert(\n                        editor.select_reference(anchor_ref.as_ref())?,\n                        reference,\n                        InsertSide::Above,\n                    )?;\n                }\n                Some(but_workspace::branch::create_reference::Anchor::AtSegment {\n                    position: but_workspace::branch::create_reference::Position::Below,\n                    ..\n                }) => {\n                    let anchor_oldest_commit = anchor_segment_oldest_commit_id\n                        .expect(\"AtSegment anchor always has oldest commit resolved\");\n                    editor.insert(\n                        editor.select_commit(anchor_oldest_commit)?,\n                        reference,\n                        InsertSide::Below,\n                    )?;\n                }\n                Some(but_workspace::branch::create_reference::Anchor::AtReference {\n                    ref_name: anchor_ref,\n                    position,\n                }) => {\n                    let side = match position {\n                        but_workspace::branch::create_reference::Position::Above => {\n                            InsertSide::Above\n                        }\n                        but_workspace::branch::create_reference::Position::Below => {\n                            InsertSide::Below\n                        }\n                    };","sourceCodeStart":653,"sourceCodeEnd":689,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-transaction/src/lib.rs#L653-L689","documentation":"Panic from `expect(\"AtSegment anchor always has oldest commit resolved\")` in `create_branch_reference` (but-transaction, lib.rs:671). Earlier in the function a first match on the anchor computes `(anchor, anchor_segment_oldest_commit_id)`; for `Anchor::AtSegment` it resolves the segment's oldest commit id. When the later editor-insert match handles `AtSegment { position: Below }` it asserts that id is present. The panic means the two matches disagree: the first pass produced None (or a non-AtSegment shape) for an anchor the second pass treats as AtSegment-Below.","triggerScenarios":"Creating a branch with `Anchor::AtSegment { ref_name, position: Below }` where the segment lookup in the first match failed to yield an oldest commit id (empty segment or a rewrite of the anchor between the two matches); any code change to the first match that stops populating `anchor_segment_oldest_commit_id` while the second match still expects it.","commonSituations":"Hitting a genuine but-transaction bug while inserting a branch below a workspace segment whose stack has no commits; upgrading but-workspace/but-transaction to mismatched versions where the Anchor enum handling changed; forks that modified one of the two anchor matches.","solutions":["Reproduce with the exact ref_name/position and capture the workspace segment layout, then report it as a but-transaction bug — this invariant is internal and not caller-fixable","As a workaround, express the same operation with `Anchor::AtReference` or `Position::Above` if your use case allows it","If you maintain the code, thread the oldest-commit id through a single match (return it from the first match for all AtSegment positions) so the second match cannot disagree"],"exampleFix":"// before (two matches that can disagree)\nlet (anchor, anchor_segment_oldest_commit_id) = match anchor { ... };\n// later:\nlet id = anchor_segment_oldest_commit_id.expect(\"AtSegment anchor always has oldest commit resolved\");\n\n// after (resolve once, fail explicitly)\nlet anchor_segment_oldest_commit_id = match anchor {\n    Some(Anchor::AtSegment { ref_name, .. }) => Some(\n        workspace.try_find_segment_and_stack_by_refname(ref_name.as_ref())?\n            .1.oldest_commit_id()\n            .context(\"AtSegment anchor must resolve an oldest commit\")?,\n    ),\n    _ => None,\n};","handlingStrategy":"try-catch","validationCode":"// Pre-check the segment actually resolves commits before anchoring below it:\nlet ws = tx.repo().workspace_snapshot()?;\nif let Some(Anchor::AtSegment { ref_name, position: Position::Below }) = &anchor {\n    let found = ws.try_find_segment_and_stack_by_refname(ref_name.as_ref())?;\n    let has_commits = found.1.tip_skip_empty().is_some();\n    if !has_commits { /* choose AtReference anchor or bail with a clear message */ }\n}","typeGuard":null,"tryCatchPattern":"// At an app boundary; this panic is an internal bug, so capture and report:\nlet outcome = std::panic::catch_unwind(AssertUnwindSafe(|| {\n    tx.create_branch_reference(ref_name, anchor, order)\n}));\nif let Err(payload) = outcome {\n    log::error!(\"but-transaction bug: AtSegment anchor unresolved: {payload:?}\");\n    // fall back to Anchor::AtReference or surface an error to the user\n}","preventionTips":["Prefer AtReference or Position::Above anchors when segment content is uncertain","Pin but-transaction/but-workspace versions together so anchor handling stays consistent","Capture the workspace segment layout in bug reports for this panic"],"tags":["rust","panic","expect","anchor","workspace-segment","branch-creation","gitbutler"],"backgroundTag":"internal-invariant-panic","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}