GitoxideLabs/gitoxide · error

could not read tix

Error message

could not read tix {label}: {err}

What it means

Tix stores its own state (e.g. review and pin entries) under prefixed refs. When enumerating these tix refs, a failure to read a specific reference that is not a benign missing-ref condition is wrapped as `could not read tix {label}: {err}`, where `label` identifies the ref family (e.g. review, pin).

Solutions

  1. Inspect and repair refs under the tix prefix (e.g. `refs/tix/...`) — remove corrupt loose ref files or stale `.lock` files
  2. Run `git fsck` to confirm ref integrity
  3. Fix filesystem permissions on the tix ref paths
  4. Delete the broken tix state ref and recreate it via the corresponding tix command (e.g. re-create the review)

Example fix

// recovery (shell) — remove a stale lock blocking a tix ref
rm .git/refs/tix/review/0001.lock
# or repair a corrupt loose ref by rewriting the object id
echo <valid-object-id> > .git/refs/tix/review/0001
Defensive patterns

Strategy: try-catch

Validate before calling

// verify tix state refs exist and are readable before loading
for prefix in ["refs/tix/review/", "refs/tix/pin/"] {
    let out = std::process::Command::new("git")
        .args(["for-each-ref", prefix, "--format=%(refname)"])
        .output()
        .expect("run git");
    if !out.status.success() {
        eprintln!("tix refs under {prefix} unreadable");
    }
}

Type guard

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

Try / catch

match load_tix_refs(&repo, "review") {
    Ok(refs) => refs,
    Err(e) if e.to_string().starts_with("could not read tix") => {
        eprintln!("tix state ref corrupt: {e}; rebuilding state");
        rebuild_tix_state(&repo)?
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling the tix-ref enumeration API (prefixed refs iteration, history.rs:1488) when a ref under the tix prefix (REVIEW_PREFIX or similar) fails to load with an error other than missing-ref — corrupt loose ref file, permission denied, or packed-refs damage under `refs/tix/...`.

Common situations: Manual deletion or editing of refs under `refs/tix/`, leftover lock files from a crashed tix run, filesystem permission changes, or interrupted writes to tix state refs.

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

Appendix: source

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

}

pub(crate) fn review_number(name: &BStr) -> Option<&BStr> {
    let suffix = name.strip_prefix(REVIEW_PREFIX)?;
    (suffix.first().is_some_and(|digit| matches!(digit, b'1'..=b'9')) && suffix.iter().all(u8::is_ascii_digit))
        .then_some(suffix.as_bstr())
}

fn refs_with_commit_targets(repo: &gix::Repository, prefix: &[u8], label: &str) -> Result<Vec<Pin>> {
    let mut out = Vec::new();
    let references = repo.references().context("could not open references")?;
    for reference in references
        .prefixed(prefix.as_bstr())
        .with_context(|| format!("could not iterate tix {label}s"))?
    {
        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 tix {label}: {err}")),
        };
        let suffix = reference.name().as_bstr().strip_prefix(prefix).unwrap_or_default();
        let valid_suffix = if prefix == REVIEW_PREFIX {
            review_number(reference.name().as_bstr()).is_some()
        } else {
            suffix.len() >= 4 && suffix.iter().all(u8::is_ascii_alphanumeric)
        };
        if !valid_suffix {
            tracing::warn!(name = %reference.name(), %label, "ignoring malformed tix reference");
            continue;
        }
        let name = reference.name().to_owned();
        let target = reference.target().into_owned();
        if let Some(target_name) = target.try_name()
            && crate::edit::undo::ref_chain_reaches_queue(repo, target_name)?
        {
            tracing::warn!(name = %name, %label, "ignoring tix reference into the undo queue");
            continue;

View on GitHub (pinned to e73179060b)