gitbutlerapp/gitbutler · error · anyhow::Error

Failed to find remote branch's corresponding remote

Error message

Failed to find remote branch's corresponding remote

What it means

Thrown by ReferenceExtGix::identity (crates/gitbutler-branch/src/reference_ext.rs) when a reference is categorized as refs/remotes/<remote>/<branch> but its shorthand name does not start with any name in the passed gix::remote::Names set. The function needs the owning remote to strip it and produce a BranchIdentity, so an unresolvable remote prefix is fatal. Note the rfind relies on remote_names being sorted ascending by length (gix::Repository::remote_names() guarantees this) to pick the longest match.

Source

Thrown at crates/gitbutler-branch/src/reference_ext.rs:32

    /// `refs/heads/my-branch` -> `my-branch`
    /// `refs/remotes/origin/my-branch` -> `my-branch`
    /// `refs/remotes/Byron/gitbutler/my-branch` -> `my-branch` (where the remote is `Byron/gitbutler`)
    fn identity(&self, remotes: &gix::remote::Names) -> Result<BranchIdentity>;
}

impl ReferenceExtGix for &gix::refs::FullNameRef {
    fn identity(&self, remotes: &gix::remote::Names) -> Result<BranchIdentity> {
        let (category, shorthand_name) = self
            .category_and_short_name()
            .context("Branch could not be categorized")?;
        if !matches!(category, Category::RemoteBranch) {
            return Ok(shorthand_name.try_into()?);
        }

        let longest_remote = remotes
            .iter()
            .rfind(|reference_name| shorthand_name.starts_with(reference_name))
            .ok_or(anyhow::anyhow!(
                "Failed to find remote branch's corresponding remote"
            ))?;

        let shorthand_name: &BStr = shorthand_name
            .strip_prefix(longest_remote.as_bytes())
            .and_then(|str| str.strip_prefix(b"/"))
            .ok_or(anyhow::anyhow!(
                "Failed to cut remote name {longest_remote} off of shorthand name {shorthand_name}"
            ))?
            .into();

        Ok(shorthand_name.try_into()?)
    }
}

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Prune stale remote-tracking refs: `git remote prune <remote>` (or `git fetch --prune`), or delete the orphan ref with `git update-ref -d refs/remotes/<unknown-remote>/<branch>`
  2. Re-add or rename back the remote so the ref prefix matches a configured remote: `git remote add <name> <url>`
  3. If calling identity() yourself, filter refs first: skip refs under refs/remotes/ whose next path component is not in repo.remote_names()
  4. If you control the data flow, pass remote_names from the same gix::Repository the reference was read from

Example fix

// before
let identity = ref.identity(&repo.remote_names())?; // errors on refs/remotes/<unknown-remote>/x

// after: skip stale remote-tracking refs whose remote no longer exists
let remotes = repo.remote_names();
if let Ok((gix::refs::Category::RemoteBranch, _)) = ref.category_and_short_name()
    && !remotes.iter().any(|r| ref.as_bstr().starts_with(format!("refs/remotes/{r}/").as_bytes()))
{
    continue; // stray remote-tracking ref, remote not configured anymore
}
let identity = ref.identity(&remotes)?;
Defensive patterns

Strategy: validation

Validate before calling

let remotes = repo.remote_names(); // sorted by length ascending
let is_resolvable = |r: &gix::refs::FullNameRef| {
    match r.category_and_short_name() {
        Ok((gix::refs::Category::RemoteBranch, short)) => {
            remotes.iter().any(|name| short.starts_with(name))
        }
        Ok(_) => true, // non remote-tracking refs never hit this error
        Err(_) => false,
    }
};
// skip refs where is_resolvable(ref) == false before calling identity()

Type guard

fn has_configured_remote(r: &gix::refs::FullNameRef, remotes: &gix::remote::Names) -> bool {
    matches!(r.category_and_short_name(), Ok((gix::refs::Category::RemoteBranch, ref short))
        if remotes.iter().any(|name| short.starts_with(name)))
}

Try / catch

for r in refs {
    match r.identity(&remotes) {
        Ok(id) => use(id),
        Err(err) if err.to_string().contains("corresponding remote") => {
            tracing::warn!("stale remote-tracking ref {}, pruning suggested", r.name);
            continue;
        }
        Err(err) => return Err(err),
    }
}

Prevention

When it happens

Trigger: Calling ref.identity(&repo.remote_names()) on a remote-tracking ref whose remote is no longer configured, e.g. refs/remotes/old-remote/feature left behind after `git remote remove old-remote` or a remote rename; passing a remote_names set from a different repository than the ref came from; refs created manually with an arbitrary prefix under refs/remotes/ that matches no configured remote.

Common situations: A teammate renames the remote (origin -> upstream) and stale refs/remotes/origin/* remain; a remote was removed but `git remote prune` never ran; refs synced between machines where one machine lacks the remote in .git/config; tests constructing FullNameRef values without registering matching remotes in the repo config.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/b4d2ba33fc7e60d3. Report an issue: GitHub.