gitbutlerapp/gitbutler · error · anyhow::Error
Cannot reconcile {source}: branch name '{name}' occurs more
Error message
Cannot reconcile {source}: branch name '{name}' occurs more than once What it means
During metadata reconciliation, `ensure_unique_branch_names()` walks all workspace branch refs and rejects the operation if the same full ref name (`refs/heads/...`) appears twice. Duplicate workspace branches would make stack/branch reconciliation ambiguous, so it refuses rather than guessing. The `source` string names which reconciliation step failed.
Source
Thrown at crates/but-core/src/ref_metadata.rs:533
config: &mut gix::config::File,
key: &str,
value: Option<impl AsRef<str>>,
) -> anyhow::Result<()> {
match value {
Some(value) => git_config::set_config_value(config, key, value.as_ref())?,
None => git_config::remove_config_value(config, key)?,
}
Ok(())
}
fn ensure_unique_branch_names<'a>(
names: impl IntoIterator<Item = &'a gix::refs::FullNameRef>,
source: &str,
) -> Result<()> {
let mut seen = Vec::<gix::refs::FullName>::new();
for name in names {
if seen.iter().any(|seen| seen.as_ref() == name) {
bail!("Cannot reconcile {source}: branch name '{name}' occurs more than once");
}
seen.push(name.to_owned());
}
Ok(())
}
fn remove_branch_from_stacks(
stacks: &mut [WorkspaceStack],
preferred_stack_idx: usize,
name: &gix::refs::FullNameRef,
) -> Option<WorkspaceStackBranch> {
if let Some(stack) = stacks.get_mut(preferred_stack_idx)
&& let Some(branch_idx) = stack
.branches
.iter()
.position(|branch| branch.ref_name.as_ref() == name)
{
return Some(stack.branches.remove(branch_idx));View on GitHub (pinned to caf1f223d3)
Solutions
- List the workspace stacks and find the duplicated ref name (the error names it), then delete or rename one of the stacks/branches so each full ref occurs once.
- Rename the duplicate branch in one stack before reconciling.
- If both copies are stale, remove the redundant stack via GitButler UI/CLI and retry the operation that triggered reconciliation.
- Report to GitButler if the duplication arose without manual recovery steps — the writer allowed an invalid state.
Defensive patterns
Strategy: validation
Validate before calling
// Rust — detect duplicates before triggering reconciliation
fn find_duplicate_branch_names<'a>(
names: impl IntoIterator<Item = &'a gix::refs::FullNameRef>,
) -> Option<String> {
let mut seen = std::collections::HashSet::new();
for name in names {
if !seen.insert(name.as_bstr().to_owned()) {
return Some(name.to_string());
}
}
None
} Try / catch
if let Some(dup) = find_duplicate_branch_names(all_refs) {
// offer the user a rename/remove of one duplicate before reconciling
return prompt_resolve_duplicate(dup);
}
reconcile(all_refs)?; Prevention
- Refuse to attach a branch ref to a second stack at write time so duplicates can't form.
- Don't restore metadata backups on top of live state; wipe or merge deliberately.
- After any manual metadata surgery, run a uniqueness check over workspace branch refs.
When it happens
Trigger: Calling the reconcile path when two stacks (or two segments) in the workspace metadata reference the same full branch ref — typically after a sync bug duplicated a stack, metadata was restored from a backup on top of existing state, or branches were merged between stacks manually.
Common situations: Two GitButler clients writing concurrently before locking was airtight; copying project data directories between machines; recovering `metadata.toml`/database from a snapshot while refs also exist in git; importing the same branch into two stacks.
Related errors
- Conflict {} of "{}" was addressed more than once
- The model returned more than one resolution for "{}"
- failed to determine remote for non remote-tracking branch {t
- Target ref required for remote url
- Remote name '{name}' already exists
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/d598ef8d87900f73.
Report an issue: GitHub.