gitbutlerapp/gitbutler · error

target ref ' ' must be a remote tracking branch

Error message

target ref '{}' must be a remote tracking branch

What it means

Initializing a GitButler project's target requires a remote-tracking branch reference, because the target defines what the workspace pushes to and fetches from. If the provided target ref's category is not a remote branch (e.g. a local branch, tag, or other ref), initialization is refused.

Solutions

  1. Pass the remote-tracking form, e.g. origin/main (refs/remotes/origin/main)
  2. Run git fetch (or the equivalent gix fetch) first so the remote-tracking branch exists
  3. If you only have a local branch, push it to a remote or create a tracking branch, then use that

Example fix

// before
but.set_target_ref_and_init_project(repo, "refs/heads/main")?;
// after
but.set_target_ref_and_init_project(repo, "refs/remotes/origin/main")?;
Defensive patterns

Strategy: validation

Validate before calling

let r = repo.find_reference(&target_ref)?;
debug_assert_eq!(r.name().category(), Some(gix::refs::Category::RemoteBranch));
// refuse non remote-tracking refs before calling

Type guard

fn is_remote_tracking_ref(name: &gix::refs::PartialNameRef) -> bool {
    name.category() == Some(gix::refs::Category::RemoteBranch)
}

Try / catch

match result {
    Err(e) if e.to_string().contains("remote tracking branch") => {
        // convert e.g. heads/main → remotes/origin/main and retry
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling set_target_ref_and_init_project with a ref whose category() is not gix::refs::Category::RemoteBranch — e.g. refs/heads/main instead of refs/remotes/origin/main.

Common situations: Passing a local branch name by mistake; a repo where the remote-tracking branch hasn't been fetched so only a local branch exists; scripts building full ref names incorrectly.

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

Appendix: source

Thrown at crates/but-workspace/src/init.rs:144

/// reachable from that target; otherwise it is replaced by the merge-base with `HEAD`. The target
/// is always validated against `HEAD` before metadata is persisted.
/// `push_remote`, if `Some`, is validated and stored; if `None`, an existing push remote
/// is kept as is.
///
/// Unlike `set_base_branch()`, this neither creates stacks, nor updates the workspace
/// commit, nor checks anything out. The caller is expected to hold exclusive worktree
/// access, and to invalidate any cached workspace projection afterwards.
pub fn set_target_ref_and_init_project(
    repo: &gix::Repository,
    target_ref: &gix::refs::FullNameRef,
    push_remote: Option<String>,
) -> Result<()> {
    let project_meta = ProjectMeta::resolve(repo)?;
    let repaired =
        but_core::ref_metadata::repair_target_metadata_for_migration(&project_meta, repo);

    if target_ref.category() != Some(gix::refs::Category::RemoteBranch) {
        bail!(
            "target ref '{}' must be a remote tracking branch",
            target_ref.as_bstr()
        );
    }

    let target_head = repo
        .try_find_reference(target_ref)?
        .with_context(|| format!("remote branch '{}' not found", target_ref.as_bstr()))?
        .peel_to_commit()
        .with_context(|| format!("failed to peel branch '{}' to commit", target_ref.as_bstr()))?
        .id;

    // Reject targets whose remote isn't configured - reads like the base-branch data
    // would fail on them later.
    let (_upstream_ref, remote) = repo
        .upstream_branch_and_remote_for_tracking_branch(target_ref)?
        .with_context(|| {
            format!(

View on GitHub (pinned to 58e5313667)