gitbutlerapp/gitbutler · error
Can only delete local branches under refs/heads, got
Error message
Can only delete local branches under refs/heads, got '{}' What it means
`branch_remove_with_perm` refuses to delete any reference whose category is not a local branch under `refs/heads`. GitButler branch deletion only operates on local branches; remote-tracking refs, tags, and other ref categories are rejected up front with the offending full refname in the message.
Solutions
- Pass a fully-qualified local branch ref, e.g. `refs/heads/my-branch`
- Strip remote prefixes and delete via the appropriate remote-deletion API instead
- Verify `ref_name.category()` is `LocalBranch` before calling
Example fix
// before
ctx.branch_remove("refs/remotes/origin/feature")?;
// after
ctx.branch_remove("refs/heads/feature")?; // local branch only Defensive patterns
Strategy: validation
Validate before calling
function isLocalBranch(ref: string): boolean {
return ref.startsWith("refs/heads/");
}
if (!isLocalBranch(refName)) throw new Error(`refuse to delete non-local branch: ${refName}`); Type guard
function isLocalBranchRef(ref: string): ref is `refs/heads/${string}` {
return ref.startsWith("refs/heads/");
} Try / catch
try {
await api.branchRemove(ref);
} catch (e) {
if (String(e).includes("Can only delete local branches under refs/heads")) {
console.warn(`${ref} is not a local branch; use remote deletion API`);
} else { throw e; }
} Prevention
- Always pass fully-qualified refs/heads/... names to delete APIs
- Filter out remote-tracking refs and tags before bulk deletes
- Resolve short branch names to refs/heads before calling
- Keep client ref-resolution logic aligned with ref categories
When it happens
Trigger: Calling `branch_remove_with_perm` (or the SDK/server branch-delete endpoint) with a ref like `refs/remotes/origin/foo`, a tag, or another non-`refs/heads` reference.
Common situations: Passing a remote-tracking branch name to the delete API; a client resolving a branch to its full refname of the wrong category; scripting deletes against tags by mistake.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Branch name ' ' collides with existing branch
- Can only check out local branches under refs/heads, got
- Can only check out local branches under refs/heads or…
- Can only rename local branches under refs/heads, got
- Cannot add the target
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/289911f89f215450.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-api/src/branch.rs:1111
#[but_api(crate::json::FullNameBytes)] ref_name: gix::refs::FullName,
) -> anyhow::Result<BranchRemoveResult> {
let mut guard = ctx.exclusive_worktree_access();
branch_remove_with_perm(ctx, ref_name, guard.write_permission())
}
/// Remove the local branch `ref_name`, whether or not it is part of the current
/// workspace projection, under caller-held exclusive repository access and
/// record an oplog snapshot on success.
///
/// See [`branch_remove()`] for the checked-out-reference behaviour and
/// [`but_workspace::branch::remove_reference()`] for the lower-level deletion.
pub fn branch_remove_with_perm(
ctx: &mut but_ctx::Context,
ref_name: gix::refs::FullName,
perm: &mut RepoExclusive,
) -> anyhow::Result<BranchRemoveResult> {
if ref_name.category() != Some(gix::refs::Category::LocalBranch) {
bail!(
"Can only delete local branches under refs/heads, got '{}'",
ref_name.as_bstr()
);
}
if but_branches::is_technical_branch_name(ref_name.shorten()) {
bail!(
"Cannot delete GitButler technical branch '{}'",
ref_name.shorten()
);
}
let maybe_oplog_entry = but_oplog::UnmaterializedOplogSnapshot::from_details_with_perm(
ctx,
SnapshotDetails::new(OperationKind::DeleteBranch)
.with_trailers([Trailer::Name(ref_name.to_string())]),
perm.read_permission(),
DryRun::No,
);View on GitHub (pinned to 58e5313667)