gitbutlerapp/gitbutler · error

AtSegment anchor always has oldest commit resolved

Error message

AtSegment anchor always has oldest commit resolved

What it means

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.

Source

Thrown at crates/but-transaction/src/lib.rs:671

                        InsertSide::Below,
                    )?;
                }
                Some(but_workspace::branch::create_reference::Anchor::AtSegment {
                    ref_name: anchor_ref,
                    position: but_workspace::branch::create_reference::Position::Above,
                }) => {
                    editor.insert(
                        editor.select_reference(anchor_ref.as_ref())?,
                        reference,
                        InsertSide::Above,
                    )?;
                }
                Some(but_workspace::branch::create_reference::Anchor::AtSegment {
                    position: but_workspace::branch::create_reference::Position::Below,
                    ..
                }) => {
                    let anchor_oldest_commit = anchor_segment_oldest_commit_id
                        .expect("AtSegment anchor always has oldest commit resolved");
                    editor.insert(
                        editor.select_commit(anchor_oldest_commit)?,
                        reference,
                        InsertSide::Below,
                    )?;
                }
                Some(but_workspace::branch::create_reference::Anchor::AtReference {
                    ref_name: anchor_ref,
                    position,
                }) => {
                    let side = match position {
                        but_workspace::branch::create_reference::Position::Above => {
                            InsertSide::Above
                        }
                        but_workspace::branch::create_reference::Position::Below => {
                            InsertSide::Below
                        }
                    };

View on GitHub (pinned to caf1f223d3)

Solutions

  1. 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
  2. As a workaround, express the same operation with `Anchor::AtReference` or `Position::Above` if your use case allows it
  3. 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

Example fix

// before (two matches that can disagree)
let (anchor, anchor_segment_oldest_commit_id) = match anchor { ... };
// later:
let id = anchor_segment_oldest_commit_id.expect("AtSegment anchor always has oldest commit resolved");

// after (resolve once, fail explicitly)
let anchor_segment_oldest_commit_id = match anchor {
    Some(Anchor::AtSegment { ref_name, .. }) => Some(
        workspace.try_find_segment_and_stack_by_refname(ref_name.as_ref())?
            .1.oldest_commit_id()
            .context("AtSegment anchor must resolve an oldest commit")?,
    ),
    _ => None,
};
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check the segment actually resolves commits before anchoring below it:
let ws = tx.repo().workspace_snapshot()?;
if let Some(Anchor::AtSegment { ref_name, position: Position::Below }) = &anchor {
    let found = ws.try_find_segment_and_stack_by_refname(ref_name.as_ref())?;
    let has_commits = found.1.tip_skip_empty().is_some();
    if !has_commits { /* choose AtReference anchor or bail with a clear message */ }
}

Try / catch

// At an app boundary; this panic is an internal bug, so capture and report:
let outcome = std::panic::catch_unwind(AssertUnwindSafe(|| {
    tx.create_branch_reference(ref_name, anchor, order)
}));
if let Err(payload) = outcome {
    log::error!("but-transaction bug: AtSegment anchor unresolved: {payload:?}");
    // fall back to Anchor::AtReference or surface an error to the user
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/eeb95f1c0fe131a4. Report an issue: GitHub.