gitbutlerapp/gitbutler · error · anyhow::Error

Refusing to {operation} symbolic ref '{}' due to potential a

Error message

Refusing to {operation} symbolic ref '{}' due to potential ambiguity

What it means

`try_find_validated_ref` (branch/mod.rs) refuses to continue an operation when the located reference's target is symbolic (`gix::refs::TargetRef::Symbolic`), i.e. the ref points at another ref rather than directly at an object id. Because dereferencing semantics would be ambiguous (act on the symref itself or its target?), the operation names the branch and bails. Missing refs are fine (`Ok(None)`); only symbolic ones are fatal.

Source

Thrown at crates/but-workspace/src/branch/mod.rs:540

}

/// Find `branch` in `repo` and reject it if it resolves to a symbolic reference.
///
/// `operation` is used only for the error message so callers such as apply and unapply can share
/// validation while still reporting the action they refused to perform.
///
/// Missing references are returned as `Ok(None)` so each caller can decide whether absence is an error or a no-op.
pub(crate) fn try_find_validated_ref<'repo>(
    repo: &'repo gix::Repository,
    branch: &gix::refs::FullNameRef,
    operation: &str,
) -> anyhow::Result<Option<gix::Reference<'repo>>> {
    let branch_ref = repo.try_find_reference(branch)?;
    if branch_ref
        .as_ref()
        .is_some_and(|r| matches!(r.target(), gix::refs::TargetRef::Symbolic(_)))
    {
        anyhow::bail!(
            "Refusing to {operation} symbolic ref '{}' due to potential ambiguity",
            branch.shorten()
        );
    }
    Ok(branch_ref)
}

/// Functions and types related to adding a branch to the workspace.
pub mod apply;
pub use apply::apply;

/// Functions and types related to removing a branch from the workspace.
pub mod unapply;
pub use unapply::function::unapply;

/// related types for removing a workspace reference.
pub mod remove_reference;
pub use remove_reference::remove_reference;

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Resolve the symref to its target ref first (`gix::Repository::find_reference(...).follow()` / peel to the direct ref) and pass the concrete branch name.
  2. Or delete/replace the symbolic ref if it was created by accident (`git update-ref --no-deref` tooling or `git symbolic-ref -d`).
  3. Check `repo.try_find_reference(branch)` target kind before invoking the operation and surface a targeted message.

Example fix

// before
let branch_ref = try_find_validated_ref(repo, &branch_name, "delete"); // bails on symref

// after
let target = repo.find_reference(&branch_name)?.follow()?.target;
let concrete: gix::refs::FullName = /* peel symbolic target to the real branch */;
let branch_ref = try_find_validated_ref(repo, &concrete.as_ref(), "delete");
Defensive patterns

Strategy: validation

Validate before calling

// Resolve symrefs before invoking operations that use try_find_validated_ref:
if let Ok(Some(r)) = repo.try_find_reference(branch) {
    if matches!(r.target(), gix::refs::TargetRef::Symbolic(_)) {
        anyhow::bail!("branch {} is a symbolic ref; resolve it to its target first", branch.shorten());
    }
}

Type guard

fn is_symbolic_ref(repo: &gix::Repository, branch: &gix::refs::FullNameRef) -> bool {
    repo.try_find_reference(branch).ok().flatten().is_some_and(|r| matches!(r.target(), gix::refs::TargetRef::Symbolic(_)))
}

Try / catch

match try_find_validated_ref(repo, branch, "delete") {
    Err(err) if err.to_string().contains("symbolic ref") => { /* follow the symref and retry with the concrete ref */ }
    other => other,
}

Prevention

When it happens

Trigger: Calling an operation that funnels through `try_find_validated_ref(repo, branch, operation)` (branch removal/move paths in but-workspace) where `branch` resolves to a symbolic ref — e.g. a hand-crafted `refs/heads/foo -> refs/heads/bar`, or certain `refs/remotes/origin/HEAD`-style aliases if passed as the branch.

Common situations: Repos with manually created symrefs (`git symbolic-ref`), mirrors where branches alias each other, or tooling that passes a HEAD-style alias where a concrete branch ref is required. The ref exists, so callers expecting 'not found' handling are surprised by the refusal.

Related errors


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