gitbutlerapp/gitbutler · error

Expected ' ' to be a remote tracking branch, but was

Error message

Expected '{ref_name}' to be a remote tracking branch, but was {category:?}

What it means

Thrown by RefInfo::for_ui when the reference it was given categorizes as something other than a remote tracking branch (e.g. a local branch, tag, or note ref). The function exists solely to build UI metadata for remote-tracking refs, so any other category is rejected. The categorization comes from gix's ref_name.category_and_short_name().

Solutions

  1. Ensure the ref is under refs/remotes/ before calling for_ui; use the tracking ref, not the local branch.
  2. Check ref_name.category_and_short_name() yourself first and pick the right UI path per category.
  3. If the branch has no upstream, create/push a tracking branch or handle the no-remote case in the caller.

Example fix

// before
RefInfo::for_ui(repo, ref_name, remotes)?; // may be a local branch

// after
let (category, _) = ref_name.category_and_short_name()?;
if category == Category::RemoteBranch {
    RefInfo::for_ui(repo, ref_name, remotes)?;
} else {
    local_ref_info(ref_name)?;
}
Defensive patterns

Strategy: type-guard

Validate before calling

fn is_remote_branch(ref_name: &gix::refs::FullNameRef) -> bool {
    matches!(ref_name.category_and_short_name(), Ok((Category::RemoteBranch, _)))
}

Type guard

// narrowing helper
fn as_remote_branch(r: &gix::refs::FullNameRef) -> Option<&str> {
    let (cat, short) = r.category_and_short_name().ok()?;
    (cat == Category::RemoteBranch).then(|| short.as_bstr().to_str().ok())?.map(str::to_owned)
}

Try / catch

match RefInfo::for_ui(...) {
    Err(e) if e.to_string().contains("remote tracking branch") => local_ref_info(ref_name)?,
    other => other?,
}

Prevention

When it happens

Trigger: Calling for_ui with a fully-qualified ref like refs/heads/main (local branch) or refs/tags/v1 instead of refs/remotes/origin/main; or after a ref rewrite changed the ref's category.

Common situations: Passing the workspace's local ref name instead of its upstream counterpart into remote-ref UI helpers; ambiguous or renamed refs after fetch/prune; callers assuming every workspace ref has a remote.

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

Appendix: source

Thrown at crates/but-workspace/src/ui/ref_info.rs:67

    pub display_name: String,
    /// The symbolic name of the remote, like `origin`, or `origin/other`.
    pub remote_name: String,
}
#[cfg(feature = "export-schema")]
but_schemars::register_sdk_type!(RemoteTrackingReference);

impl RemoteTrackingReference {
    /// Create a new instance from `ref_name` and `remote_names`, essentially splitting the remote
    /// name off the short name.
    pub fn for_ui(
        ref_name: gix::refs::FullName,
        remote_names: &gix::remote::Names,
    ) -> anyhow::Result<Self> {
        let (category, _short_name) = ref_name.category_and_short_name().with_context(|| {
            format!("Failed to categorize presumed remote reference '{ref_name}'")
        })?;
        if category != Category::RemoteBranch {
            bail!("Expected '{ref_name}' to be a remote tracking branch, but was {category:?}");
        }
        let (longest_remote, short_name) =
            extract_remote_name_and_short_name(ref_name.as_ref(), remote_names)
                .ok_or(anyhow::anyhow!(
                    "Failed to find remote branch's corresponding remote"
                ))
                .with_context(|| {
                    format!(
                        "Remote reference '{ref_name}' couldn't be matched with any known remote"
                    )
                })?;

        Ok(RemoteTrackingReference {
            display_name: short_name.to_str_lossy().into_owned(),
            remote_name: longest_remote,
            full_name_bytes: ref_name.into_inner(),
        })
    }

View on GitHub (pinned to 58e5313667)