GitoxideLabs/gitoxide · error

could not read local branch

Error message

could not read local branch: {err}

What it means

While building the set of local branches for history decoration, tix iterates the repository's branch references. If reading an individual reference fails for a reason other than a transient missing-ref condition (e.g. packed-refs vs loose ref I/O errors, permission problems), the error is wrapped as `could not read local branch: {err}` and returned instead of skipping the branch.

Solutions

  1. Fix filesystem permissions on `.git/refs` and `.git/packed-refs`
  2. Run `git fsck` to find and repair corrupt or dangling ref files
  3. Remove stale lock files under `.git/refs` if a previous operation crashed
  4. Identify the failing branch name from the wrapped error and delete/repair that specific ref
Defensive patterns

Strategy: try-catch

Validate before calling

// check refs are readable before collecting branches
fn refs_readable(git_dir: &std::path::Path) -> bool {
    let heads = git_dir.join("refs/heads");
    match std::fs::read_dir(&heads) {
        Ok(entries) => entries.all(|e| e.map(|e| e.metadata().map(|m| !m.permissions().readonly()).unwrap_or(true)).unwrap_or(false)),
        Err(_) => git_dir.join("packed-refs").exists(),
    }
}

Type guard

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

Try / catch

match collect_local_branches(&repo) {
    Ok(branches) => branches,
    Err(e) if e.to_string().starts_with("could not read local branch") => {
        eprintln!("branch refs unreadable: {e}; repairing refs");
        repair_refs(&repo)?;
        collect_local_branches(&repo)?
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling the branch-collection API (the function preparing local branches for peeling, e.g. during history load) when `repo.references()` yields an `Err` on a branch ref that `is_missing_ref` does not classify as ignorable — such as I/O or permission errors reading loose or packed refs.

Common situations: Filesystem permission issues on `.git/refs`, partially written or corrupt loose ref files, packed-refs locks, or network/filesystem mount problems on remotes-backed worktrees.

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

Appendix: source

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

        .enumerate()
        .filter(|(_, flags)| flags & (VISIBLE | HIDDEN) == VISIBLE | HIDDEN)
        .map(|(index, _)| graph.id(CommitIndex(index as u32)))
        .collect())
}

fn local_refs_by_target(repo: &gix::Repository) -> Result<HashMap<ObjectId, Vec<BString>>> {
    let mut out = HashMap::<ObjectId, Vec<BString>>::new();
    let platform = repo.references().context("could not open references")?;
    let refs = platform
        .local_branches()
        .context("could not iterate local branches")?
        .peeled()
        .context("could not prepare local branches for peeling")?;
    for reference in refs {
        let reference = match reference {
            Ok(reference) => reference,
            Err(err) if is_missing_ref(&*err) => continue,
            Err(err) => return Err(anyhow::anyhow!("could not read local branch: {err}")),
        };
        out.entry(reference.id().detach())
            .or_default()
            .push(reference.name().as_bstr().to_owned());
    }
    Ok(out)
}

fn resolve_tracking(repo: &gix::Repository, names: &[BString]) -> Result<Vec<SelectionRef>> {
    let mut out = Vec::with_capacity(names.len());
    for full_name in names {
        let Some(reference) = repo
            .try_find_reference(full_name.as_bstr())
            .with_context(|| format!("could not read local branch {full_name}"))?
        else {
            continue;
        };
        let upstream = reference

View on GitHub (pinned to e73179060b)