gitbutlerapp/gitbutler · error · anyhow::Error

failed to determine remote for non remote-tracking branch {t

Error message

failed to determine remote for non remote-tracking branch {target_ref}

What it means

While deriving the push remote for a stack target, `push_remote_name()` falls back to the remote encoded in `target_ref`. It first tries configured remote names; if that fails it requires the ref's category to be `refs/remotes/...`. A target ref like `refs/heads/main` or any non-remote-tracking ref triggers this bail — the metadata simply has no remote to push to.

Source

Thrown at crates/but-core/src/ref_metadata.rs:333

    /// remote behind [`Self::target_ref`].
    ///
    /// If no configured remote matches the target ref, fall back to the first path component
    /// after `refs/remotes/`, the textual remote name that legacy metadata stored verbatim.
    pub fn push_remote_name(&self, repo: &gix::Repository) -> Result<String> {
        if let Some(name) = self.push_remote.clone() {
            return Ok(name);
        }
        let target_ref = self.target_ref_or_err()?;
        if let Some((remote_name, _short_name)) =
            extract_remote_name_and_short_name(target_ref.as_ref(), &repo.remote_names())
        {
            return Ok(remote_name);
        }
        let (category, short_name) = target_ref
            .category_and_short_name()
            .with_context(|| format!("failed to determine remote for branch {target_ref}"))?;
        if category != gix::refs::Category::RemoteBranch {
            bail!("failed to determine remote for non remote-tracking branch {target_ref}");
        }
        let slash_pos = short_name.find_byte(b'/').with_context(|| {
            format!("remote tracking branch {target_ref} didn't have '/' in its short name")
        })?;
        let remote_name = short_name[..slash_pos].to_str_lossy().into_owned();
        tracing::warn!(
            "remote '{remote_name}' of target ref {target_ref} is not configured in git config"
        );
        Ok(remote_name)
    }

    /// Get the fetch URL of the remote behind [`Self::target_ref`].
    pub fn remote_url_with_fallback(&self, repo: &gix::Repository) -> Result<String> {
        let Some(target_ref) = self.target_ref.as_ref() else {
            bail!("Target ref required for remote url")
        };
        let remote_names = repo.remote_names();
        let (remote_name, _short_name) =

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Set an explicit push remote in the stack/target metadata (`push_remote`) so the fallback path is never taken.
  2. Re-add and fetch the remote so the target ref can be remote-tracking: `git remote add origin <url> && git fetch origin`, then re-onboard/re-target the stack to `refs/remotes/origin/<branch>`.
  3. Check `git remote -v` and `git config --get-regexp '^remote\.'` to confirm the remote the target ref's first component refers to actually exists.
  4. If the remote was renamed, update the stored target ref to the new `refs/remotes/<new-name>/...`.

Example fix

# before: target_ref = refs/heads/main (local branch, no remote)
# after
git remote add origin git@github.com:org/repo.git
git fetch origin
git branch --set-upstream-to=origin/main main   # target becomes refs/remotes/origin/main
Defensive patterns

Strategy: validation

Validate before calling

// Rust — ensure the target ref is remote-tracking (or push_remote set) before pushing
fn push_remote_resolvable(
    meta: &but_core::ref_metadata::StackMetadata,
    repo: &gix::Repository,
) -> bool {
    meta.push_remote.is_some()
        || meta
            .target_ref
            .as_ref()
            .and_then(|r| {
                let names = repo.remote_names();
                gix::refs::transaction::RefLogAngle::default(); // keep clippy quiet in snippets
                but_core::ref_metadata::extract_remote_name_and_short_name(r.as_ref(), &names)
                    .map(|(name, _)| names.contains(&name))
            })
            .unwrap_or(false)
}

Try / catch

let remote_name = match meta.push_remote_name(&repo) {
    Ok(name) => name,
    Err(err) if err.to_string().contains("non remote-tracking branch") => {
        // metadata target is a local branch: ask the user to pick a remote-tracking target
        // or configure push_remote, instead of surfacing a raw error
        prompt_user_to_set_push_remote(&meta)?
    }
    Err(err) => return Err(err),
};

Prevention

When it happens

Trigger: Calling `push_remote_name(&repo)` on stack/target metadata whose `target_ref` is a local branch (`refs/heads/...`) while `push_remote` is unset — e.g. a project whose target was onboarded from a local branch, or legacy metadata storing a non-remote-tracking ref.

Common situations: Repository cloned without a remote or remote removed/renamed after onboarding (`origin` deleted); target set to a local branch during early GitButler setup; refs renamed via `git remote rename` so stored target refs no longer match `refs/remotes/<name>/...`.

Related errors


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