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
- Set an explicit push remote in the stack/target metadata (`push_remote`) so the fallback path is never taken.
- 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>`.
- Check `git remote -v` and `git config --get-regexp '^remote\.'` to confirm the remote the target ref's first component refers to actually exists.
- 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
- Require a fetched remote at project onboarding time so target refs are always `refs/remotes/<remote>/...`.
- Set an explicit `push_remote` in metadata when the remote name ever differs from the target's first path component.
- After `git remote rename`, update stored target refs and push_remote values.
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
- Neither found push remote found in workspace nor unambiguous
- Gerrit push flags (--wip, --ready, --hashtag/--tag, --topic,
- push failed
- push failed after {MAX_RETRIES} attempts
- Could not restore all review targets after the push failed:
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/02527c8a2e6c5bac.
Report an issue: GitHub.