gitbutlerapp/gitbutler · error

Expected a local branch, but

Error message

Expected a local branch, but '{}' is a remote-tracking branch

What it means

resolve_local_branch found a reference with the given name but of category RemoteBranch. `but branch update` only operates on local branches, so it rejects remote-tracking refs like refs/remotes/origin/main.

Solutions

  1. Use the local branch name without the remote prefix: `main` instead of `origin/main`.
  2. If only the remote branch exists, check out a local tracking branch first.
  3. Update a local branch that tracks the remote instead of the remote ref itself.

Example fix

// before
but branch update origin/main --to abc123
// after
but branch update main --to abc123
Defensive patterns

Strategy: validation

Validate before calling

if (name.startsWith('origin/') || name.startsWith('refs/remotes/')) {
  name = name.replace(/^(refs\/remotes\/)?origin\//, '');
}

Prevention

When it happens

Trigger: Passing a remote-tracking ref (e.g. `origin/main` or `refs/remotes/origin/main`) to `but branch update`, causing normalize_local_branch_ref's refs/ handling or the lookup to land on a RemoteBranch-category reference.

Common situations: Users habitually prefixing branches with `origin/` as in git workflows; scripts parameterized with remote refs; IDE-provided refs copied from the remote branch list.

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/af1ad8b934460549. Report an issue: GitHub.

Appendix: source

Thrown at crates/but/src/command/branch/update.rs:144

                        commit_id: commit_id.into(),
                    }
                }
            })
            .collect(),
    }
}

fn resolve_local_branch(ctx: &but_ctx::Context, branch: &str) -> anyhow::Result<FullName> {
    let candidate = normalize_local_branch_ref(branch)?;
    let repo = ctx.repo.get()?;
    let Some(reference) = repo.try_find_reference(candidate.as_ref())? else {
        bail!("Local branch '{}' not found", candidate.shorten());
    };
    let reference = reference.detach();
    match reference.name.category() {
        Some(Category::LocalBranch) => Ok(reference.name),
        Some(Category::RemoteBranch) => {
            bail!(
                "Expected a local branch, but '{}' is a remote-tracking branch",
                reference.name.shorten()
            )
        }
        _ => bail!(
            "Expected a local branch, but '{}' is not under refs/heads/",
            reference.name.shorten()
        ),
    }
}

fn normalize_local_branch_ref(branch: &str) -> anyhow::Result<FullName> {
    let full = if branch.starts_with("refs/heads/") {
        branch.to_owned()
    } else if branch.starts_with("refs/") {
        bail!("Only local branches under refs/heads/ are supported");
    } else {
        format!("refs/heads/{branch}")

View on GitHub (pinned to 58e5313667)