gitbutlerapp/gitbutler · error
Can only check out local branches under refs/heads or…
Error message
Can only check out local branches under refs/heads or remote-tracking branches under refs/remotes, got '{}' What it means
`branch_checkout` only accepts local branches under `refs/heads` or remote-tracking branches under `refs/remotes` (the latter resolved to their local tracking branch via `local_tracking_branch`). Any other reference category — tags, detached names, custom namespaces — is rejected with the offending refname in the message.
Solutions
- Pass a full `refs/heads/<name>` local branch ref, or `refs/remotes/<remote>/<name>` to check out its tracking branch
- To get a tag's content, create a branch at the tag and check out that branch instead
- Normalize/qualify the refname before calling so `category()` can classify it
Example fix
// before
await api.checkoutBranch("refs/tags/v1.0");
// after
await api.createBranch({ name: "release-v1.0", startpoint: "refs/tags/v1.0" });
await api.checkoutBranch("refs/heads/release-v1.0"); Defensive patterns
Strategy: validation
Validate before calling
function isCheckoutableRef(ref: string): boolean {
return ref.startsWith("refs/heads/") || ref.startsWith("refs/remotes/");
}
if (!isCheckoutableRef(ref)) throw new Error(`cannot checkout ref: ${ref}`); Type guard
function isCheckoutableRef(ref: string): ref is `refs/heads/${string}` | `refs/remotes/${string}` {
return ref.startsWith("refs/heads/") || ref.startsWith("refs/remotes/");
} Try / catch
try {
await api.checkoutBranch(ref);
} catch (e) {
if (String(e).includes("Can only check out local branches")) {
console.warn("checkout requires refs/heads/* or refs/remotes/*; create a branch at the tag instead");
} else { throw e; }
} Prevention
- Qualify ref names (refs/heads/..., refs/remotes/...) before checkout
- Route tag checkouts through a temporary branch
- Use local_tracking_branch resolution for remotes on the client side
- Validate ref category before sending checkout requests from scripts
When it happens
Trigger: Calling the checkout API with a full refname whose `category()` is neither `LocalBranch` nor `RemoteBranch`, e.g. a tag (`refs/tags/v1.0`), a custom ref namespace, or an unqualified/invalid name that resolves to no known category.
Common situations: Trying to check out a tag through the branch-checkout endpoint; passing a raw object ID or symbolic ref; custom refs created by other tooling; client sending short names that fail categorization.
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
- Can only check out local branches under refs/heads, got
- Branch name ' ' collides with existing branch
- Can only delete local branches under refs/heads, got
- 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/abf980298668a20e.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-api/src/branch.rs:1687
}
/// Checks out a branch under caller-held exclusive repository access without
/// creating an oplog entry.
///
/// See [`branch_checkout()`] for the accepted branch names.
pub fn branch_checkout_with_perm_only(
ctx: &mut but_ctx::Context,
reference_name: gix::refs::FullName,
perm: &mut RepoExclusive,
) -> anyhow::Result<BranchCheckoutResult> {
{
let repo = ctx.repo.get()?;
let reference_name = match reference_name.category() {
Some(gix::refs::Category::LocalBranch) => reference_name,
Some(gix::refs::Category::RemoteBranch) => {
but_workspace::branch::local_tracking_branch(&repo, reference_name.as_ref())?
}
_ => bail!(
"Can only check out local branches under refs/heads or remote-tracking branches under refs/remotes, got '{}'",
reference_name.as_bstr()
),
};
let current_head = repo
.head_id()
.context("Cannot check out a branch while HEAD is unborn")?
.detach();
let mut reference = repo
.find_reference(reference_name.as_ref())
.with_context(|| format!("Could not find ref '{}'", reference_name.as_bstr()))?;
let target = reference
.peel_to_id()
.with_context(|| format!("Could not resolve ref '{}'", reference_name.as_bstr()))?
.detach();
let target_commit = repo.find_commit(target).with_context(|| {
format!(
"Ref '{}' does not point to a commit",View on GitHub (pinned to 58e5313667)