gitbutlerapp/gitbutler · error · anyhow::Error

Can't handle reference '{rn}' of category '{category:?}'

Error message

Can't handle reference '{rn}' of category '{category:?}'

What it means

Thrown by the anonymization walk in but-graph's debug module (crates/but-graph/src/debug.rs:95). When producing anonymized debug output (e.g., `but graph debug` visualizations), every reference name in the graph is rewritten to a stable fake name; the match on Category simply has no anonymization arm for Note, PseudoRef, MainPseudoRef, MainRef, LinkedPseudoRef, LinkedRef, Bisect, Rewritten, or WorktreePrivate refs. Hitting it means the repository graph contains one of those ref kinds and the anonymizer chose to fail loudly rather than leak or mangle the real name.

Source

Thrown at crates/but-graph/src/debug.rs:95

                    let new_short_name = name_mapping
                        .entry(short_name.to_owned())
                        .or_insert_with(|| int_to_alpha(num_names).into());
                    new_name.push_byte(b'/');
                    new_name.push_str(new_short_name);
                    *rn = gix::refs::FullName::try_from(new_name.as_bstr())
                        .expect("Our replacement names are always valid");
                }

                Category::Note
                | Category::PseudoRef
                | Category::MainPseudoRef
                | Category::MainRef
                | Category::LinkedPseudoRef { .. }
                | Category::LinkedRef { .. }
                | Category::Bisect
                | Category::Rewritten
                | Category::WorktreePrivate => {
                    bail!("Can't handle reference '{rn}' of category '{category:?}'");
                }
            }
            Ok(())
        };
        for node in self.inner.node_weights_mut() {
            if let Some(ri) = node.ref_info.as_mut() {
                anon(&mut ri.ref_name)?;
            }
            if let Some(rn) = node.remote_tracking_ref_name.as_mut() {
                anon(rn)?;
            }
            for ri in node.commits.iter_mut().flat_map(|c| c.refs.iter_mut()) {
                anon(&mut ri.ref_name)?;
            }
            if let Some(SegmentMetadata::Workspace(md)) = node.metadata.as_mut() {
                for rn in md
                    .stacks
                    .iter_mut()

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Inspect which ref triggered it (the message names ref and category): `git for-each-ref --format='%(refname)' | grep -E 'notes|bisect|rewritten'`
  2. Clean the transient state: finish or reset `git bisect reset`, complete the rebase, drop stale refs/rewritten and refs/notes entries you do not need, then rerun
  3. If you need anonymized output despite those refs, extend the match in debug.rs with mapping arms for the listed categories
  4. Otherwise use the non-anonymized debug output for that repo

Example fix

// before (debug.rs) — categories with no anonymization arm bail
Category::Note | Category::Bisect | Category::Rewritten | Category::WorktreePrivate => {
    bail!("Can't handle reference '{rn}' of category '{category:?}'");
}

// after — map them to a category-tagged placeholder instead of failing
Category::Note => {
    *rn = gix::refs::FullName::try_from(format!("refs/anon-notes/{}", counter.next()))
        .expect("generated name is valid");
}
Category::Bisect | Category::Rewritten | Category::WorktreePrivate
| Category::PseudoRef | Category::MainPseudoRef | Category::MainRef
| Category::LinkedPseudoRef { .. } | Category::LinkedRef { .. } => {
    *rn = gix::refs::FullName::try_from(format!("refs/anon/{category:?}/{}", counter.next()))
        .expect("generated name is valid");
}
Defensive patterns

Strategy: fallback

Validate before calling

// Pre-filter refs the anonymizer cannot handle before generating debug output
use but_graph::Category;
fn anonymizable(name: &gix::refs::FullName) -> bool {
    !matches!(
        name.category(),
        Some(Category::Note)
            | Some(Category::PseudoRef)
            | Some(Category::MainPseudoRef)
            | Some(Category::MainRef)
            | Some(Category::LinkedPseudoRef { .. })
            | Some(Category::LinkedRef { .. })
            | Some(Category::Bisect)
            | Some(Category::Rewritten)
            | Some(Category::WorktreePrivate)
    )
}

Try / catch

match graph_debug.anonymize() { // conceptual
    Ok(out) => out,
    Err(err) if err.to_string().contains("Can't handle reference") => {
        tracing::warn!("repo has non-anonymizable refs (notes/bisect/rewritten/worktree); emitting non-anonymized output");
        graph_debug.raw()
    }
    Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: Running graph debug/anonymization on a repository that has refs/notes/*, refs/bisect/* (mid git bisect), refs/rewritten/* (from rebase --update-refs), worktree-private refs (per-worktree HEAD/bisect state), or pseudo-refs like FETCH_HEAD/MERGE_REF at non-standard names; linked worktrees with their own branch namespaces.

Common situations: Debugging a graph dump right after a `git bisect` or `git rebase --update-refs` session; repos with git-notes enabled; running the anonymizer in CI fixtures that synthesize exotic refs; new but-graph versions adding categories without extending the debug anonymizer.

Related errors


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