GitoxideLabs/gitoxide · error

could not read reference

Error message

could not read reference: {err}

What it means

When collecting reference decorations for history, tix iterates all references in the repository. Most errors reading an individual ref are skipped only if they indicate the ref went missing mid-iteration; any other failure is wrapped as `could not read reference: {err}` and aborts the decoration pass.

Solutions

  1. Run `git fsck` and inspect `.git/refs` / `.git/packed-refs` for corruption
  2. Fix permissions on the `.git` directory and ref files
  3. Stop concurrent git/tix processes that may be rewriting refs, then retry
  4. Identify the specific failing ref from the wrapped error and repair or delete it
Defensive patterns

Strategy: try-catch

Validate before calling

// sanity-check all refs are readable before decoration pass
let output = std::process::Command::new("git")
    .args(["for-each-ref", "--format=%(refname)"])
    .output()
    .expect("run git");
assert!(output.status.success(), "refs unreadable: {}", String::from_utf8_lossy(&output.stderr));

Type guard

fn is_benign_ref_error(err: &dyn std::error::Error) -> bool {
    err.to_string().to_lowercase().contains("not found")
}

Try / catch

match collect_ref_decorations(&repo, include_tags) {
    Ok(decorations) => decorations,
    Err(e) if e.to_string().starts_with("could not read reference") => {
        eprintln!("a ref is unreadable: {e}");
        repair_refs(&repo)?;
        collect_ref_decorations(&repo, include_tags)?
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling the all-references iteration API (history decoration collection, around history.rs:1277) when the refs iterator returns an `Err` that is not a missing-ref condition — e.g. an unreadable loose ref, corrupt packed-refs entry, or permission error on any ref including tags and remote branches.

Common situations: Concurrent ref updates corrupting loose refs during iteration, restricted filesystem permissions, damaged packed-refs, or a repository on a flaky network mount.

Understand the failure class

Background: "git command failed": what it means when a tool shells out to git and git exits non-zero — this error's family across 21 libraries.

Related errors


AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08). Data as JSON: /api/errors/e352d14994bf8ec2. Report an issue: GitHub.

Appendix: source

Thrown at gix-tix/src/history.rs:1277

    revisions: &[OsString],
    hidden: &[OsString],
    include_worktrees: bool,
) -> Result<RefSnapshot> {
    snapshot_ignoring_pin(repo, revisions, hidden, include_worktrees, None)
}

pub(crate) fn ref_tree_revisions(repo: &gix::Repository, include_tags: bool) -> Result<Vec<OsString>> {
    let mut out = Vec::new();
    for reference in repo
        .references()
        .context("could not open references")?
        .all()
        .context("could not iterate references")?
    {
        let mut reference = match reference {
            Ok(reference) => reference,
            Err(err) if is_missing_ref(&*err) => continue,
            Err(err) => return Err(anyhow::anyhow!("could not read reference: {err}")),
        };
        let name = reference.name().as_bstr().to_owned();
        let kind = decoration_kind(&name);
        if matches!(kind, DecorationKind::Special | DecorationKind::Pin)
            || matches!(kind, DecorationKind::Tag) && !include_tags
            || name.starts_with(STASH_PREFIX)
            || name.starts_with(REVIEW_STASH_PREFIX)
        {
            continue;
        }
        let Ok(id) = reference.peel_to_id() else { continue };
        if repo.find_header(id)?.kind() != gix::object::Kind::Commit {
            continue;
        }
        out.push(gix::path::from_bstr(&name).into_owned().into_os_string());
    }
    if repo.head().is_ok_and(|head| head.referent_name().is_none()) {
        out.push("HEAD".into());

View on GitHub (pinned to e73179060b)