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

  1. Pass a full `refs/heads/<name>` local branch ref, or `refs/remotes/<remote>/<name>` to check out its tracking branch
  2. To get a tag's content, create a branch at the tag and check out that branch instead
  3. 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

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


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)