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
- 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
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
- 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
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
- a committed transaction always materializes a workspace
- if it was found before it will be found as id
- anchor is always present in the order at this point
- checked all selectors are present
- validated non-empty squash commit list
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/eeb95f1c0fe131a4.
Report an issue: GitHub.