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
- Resolve the symref to its target ref first (`gix::Repository::find_reference(...).follow()` / peel to the direct ref) and pass the concrete branch name.
- Or delete/replace the symbolic ref if it was created by accident (`git update-ref --no-deref` tooling or `git symbolic-ref -d`).
- 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
- Pass concrete branch refs, never aliases or HEAD-style symrefs.
- Follow symbolic targets with gix's Reference::follow() before calling ref-mutating APIs.
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
- Integration steps cannot be empty
- line {line_number}: pick does not accept a message clause
- line {line_number}: pick requires exactly one commit
- line {line_number}: merge does not accept a message clause
- line {line_number}: merge requires exactly one commit
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/3dabc66aea132c72.
Report an issue: GitHub.