gitbutlerapp/gitbutler · error
Could not delete branch '{}'
Error message
Could not delete branch '{}' What it means
Mid-rename failure in but-api's branch rename: a backup recovery ref was created, then deleting the old reference failed; the original gix delete error is wrapped with 'Could not delete branch <ref>' and the backup ref is cleaned up (its own cleanup failure is only warned about). The new ref is never created, so the branch keeps its old name — the rename aborts with state restored.
Source
Thrown at crates/but-api/src/branch.rs:1372
)
.with_context(|| {
format!(
"Could not create recovery ref before renaming '{}'",
ref_name.as_bstr()
)
})?,
);
if let Err(delete_err) = old_reference.delete() {
if let Some(backup) = backup_reference.take()
&& let Err(err) = backup.delete()
{
warn!(
?err,
"failed to remove branch-rename recovery ref after source deletion failed"
);
}
return Err(anyhow::Error::new(delete_err)
.context(format!("Could not delete branch '{}'", ref_name.as_bstr())));
}
if let Err(create_err) = repo.reference(
new_ref.as_ref(),
target_id,
PreviousValue::MustNotExist,
"rename branch",
) {
let create_err = anyhow::Error::new(create_err)
.context(format!("Could not create branch '{}'", new_ref.as_bstr()));
match repo.reference(
ref_name.as_ref(),
target_id,
PreviousValue::MustNotExist,
"restore branch after failed rename",
) {
Ok(_) => {
if let Some(backup) = backup_reference.take()View on GitHub (pinned to 2497b8007a)
Solutions
- Check for .git/refs/heads/<name>.lock and remove it if no git process is running.
- Retry after the competing git operation finishes; verify with `git branch --list`.
- Check filesystem permissions on .git/refs and .git/packed-refs.
- If it recurs, capture the full anyhow chain ({:#}) to see the underlying gix error.
Example fix
// before
rename_branch(&ctx, &old_name, &new_name)?;
// after: clear a stale lock first
let lock = repo.git_dir().join(format!("refs/heads/{}.lock", old_short_name));
if lock.exists() && !any_git_process_alive() {
std::fs::remove_file(&lock)?;
}
rename_branch(&ctx, &old_name, &new_name)?; Defensive patterns
Strategy: retry
Validate before calling
let lock = repo.git_dir().join(format!("refs/heads/{}.lock", old_short_name));
if lock.exists() {
// another op is in flight or a stale lock survived a crash; resolve before renaming
} Try / catch
for attempt in 0..2 {
match rename_branch(&ctx, &old_name, &new_name) {
Err(e) if attempt == 0 && e.to_string().contains("Could not delete branch") => {
clean_stale_locks(&repo); // then retry once
continue;
}
r => break r,
}
}? Prevention
- Serialize branch mutations in the app; avoid concurrent renames.
- Clean up .lock files when detecting crashed prior runs.
- On Windows, expect transient antivirus locks and retry with backoff.
When it happens
Trigger: gix delete_reference failing on refs/heads/<name>: a stale refs/heads/<name>.lock from a crashed git process, packed-refs write contention, filesystem permission errors, or a concurrent process (another git or GitButler instance) mutating the same ref.
Common situations: Two tools renaming/deleting the same branch simultaneously; leftover .lock files after a crash; read-only mounts; antivirus briefly locking files on Windows.
Related errors
- Could not create branch '{}'
- Cannot currently work in repositories without a worktree
- No uncommitted changes found for the selected files
- Failed to determine target commit for hunk absorption due to
- Stack has no branches
AI-assisted analysis of gitbutlerapp/gitbutler@2497b8007a (2026-08-17).
Data as JSON: /api/errors/e38807d4a356a848.
Report an issue: GitHub.