gitbutlerapp/gitbutler · error · but_error::Code
BranchNotFound
BranchNotFound
Error message
branch with ID {stack_id} not found What it means
While applying BranchUpdateRequests (e.g. stack reordering), every update must reference a stack that exists in the current workspace metadata and is_in_workspace() (applied). A request carrying the id of a deleted, unapplied, or unknown stack fails with Code::BranchNotFound before any order change is written.
Source
Thrown at crates/but-api/src/legacy/virtual_branches.rs:311
}
Ok(())
}
fn apply_stack_order_updates(
workspace: &mut but_core::ref_metadata::Workspace,
updates: Vec<BranchUpdateRequest>,
) -> Result<bool> {
let mut requested_orders = HashMap::new();
for update in updates {
let stack_id = update.id.context("BUG(opt-stack-id)")?;
if !workspace
.stacks
.iter()
.any(|stack| stack.id == stack_id && stack.is_in_workspace())
{
return Err(anyhow!("branch with ID {stack_id} not found")
.context(but_error::Code::BranchNotFound));
}
if let Some(order) = update.order {
requested_orders.insert(stack_id, order);
}
}
if requested_orders.is_empty() {
return Ok(false);
}
let original_stack_ids = workspace
.stacks
.iter()
.map(|stack| stack.id)
.collect::<Vec<_>>();
let original_orders = original_stack_idsView on GitHub (pinned to 2497b8007a)
Solutions
- Refetch the current stacks and resend the update with live ids
- Re-apply the stack if it should still be orderable
- Filter out ids of stacks no longer in the workspace before sending the batch
Example fix
// before: batch built from a stale snapshot
update_stack_orders(ctx, stale_updates)?;
// after: intersect with live workspace stacks
let live: HashSet<StackId> = workspace_stacks(ctx)?.iter().filter_map(|s| s.id).collect();
let updates: Vec<_> = stale_updates.into_iter()
.filter(|u| u.id.map(|id| live.contains(&id)).unwrap_or(false))
.collect();
if !updates.is_empty() { update_stack_orders(ctx, updates)?; } Defensive patterns
Strategy: validation
Validate before calling
let ws = current_workspace(ctx)?;
let live: HashSet<_> = ws.stacks.iter()
.filter(|s| s.is_in_workspace())
.filter_map(|s| s.id)
.collect();
let updates: Vec<_> = requested.into_iter()
.filter(|u| u.id.is_some_and(|id| live.contains(&id)))
.collect();
if updates.is_empty() { return Ok(false); } Type guard
use but_error::{AnyhowContextExt, Code};
fn is_branch_not_found(err: &anyhow::Error) -> bool {
err.custom_context().is_some_and(|c| c.code == Code::BranchNotFound)
} Try / catch
match update_stack_orders(ctx, updates) {
Err(err) if is_branch_not_found(&err) => {
// refetch stacks and retry with refreshed ids; drop deleted ones
}
other => other,
} Prevention
- Build reorder batches from the freshest workspace listing, never cached state
- Invalidate client-side id caches after unapplying or deleting stacks
- Re-fetch and retry on BranchNotFound instead of surfacing it to users
When it happens
Trigger: Sending stack order updates containing an id that was unapplied or deleted after the client fetched the list; two clients racing where one deletes a stack while the other reorders; SDK scripts reusing cached ids.
Common situations: Drag-to-reorder racing an unapply; frontend state holding stale stack ids; batch updates built from an old workspace snapshot.
Related errors
- Branch not found: {branch_name}
- No stacks found in the workspace
- Stack has no ID
- Unable to determine target commit for unassigned change: {}
- textFromToolResult(result)
AI-assisted analysis of gitbutlerapp/gitbutler@2497b8007a (2026-08-17).
Data as JSON: /api/errors/256b42c1d9945447.
Report an issue: GitHub.