GitoxideLabs/gitoxide · error

could not decode commit-graph parent

Error message

could not decode commit-graph parent: {err}

What it means

When building history from cached commits, tix reads each commit's parents from the commit-graph. If decoding a parent entry from the commit-graph fails (corrupt or truncated graph file, hash mismatch, unsupported format), the error is wrapped with this message and propagated to callers like `for_commits`, `schedule_cached`, and `load`.

Solutions

  1. Delete the commit-graph file(s) (`.git/objects/info/commit-graph` and the commit-graphs directory) and regenerate with `git commit-graph write` or `gix` equivalent
  2. Run `git fsck` to detect broader object-store corruption and restore from a clone/backup if needed
  3. Re-clone or restore the repository from a known-good source if the graph and objects are both damaged
  4. Verify the repository's object hash matches the commit-graph format in use

Example fix

// recovery (shell)
rm -rf .git/objects/info/commit-graph .git/objects/info/commit-graphs
git commit-graph write --reachable
Defensive patterns

Strategy: fallback

Validate before calling

// check commit-graph health before cached traversal
if std::path::Path::new(".git/objects/info/commit-graph").exists() {
    let ok = std::process::Command::new("git")
        .args(["commit-graph", "verify"])
        .status()
        .map(|s| s.success())
        .unwrap_or(false);
    if !ok {
        eprintln!("commit-graph corrupt; regenerating");
        let _ = std::fs::remove_file(".git/objects/info/commit-graph");
    }
}

Type guard

fn commit_graph_is_valid(repo_path: &std::path::Path) -> bool {
    std::process::Command::new("git")
        .args(["-C"])
        .arg(repo_path)
        .args(["commit-graph", "verify"])
        .status()
        .map(|s| s.success())
        .unwrap_or(false)
}

Try / catch

match load_history(&repo) {
    Ok(h) => h,
    Err(e) if e.to_string().contains("commit-graph parent") => {
        eprintln!("commit-graph unreadable; falling back to object walk");
        remove_commit_graph(&repo)?;
        load_history(&repo)?
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling history loading/scheduling APIs (`for_commits`, `schedule_cached`, `load`) while the repository's commit-graph file is corrupt, truncated, or contains a parent position that cannot be decoded for the traversed commit.

Common situations: A crash or disk-full event corrupted `.git/objects/info/commit-graph`, an interrupted `git commit-graph write`, a version mismatch between the graph file and the object hash, or a repository copied incompletely.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


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

Appendix: source

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

                for token in iter {
                    match token.context("could not decode cached history commit")? {
                        Token::Tree { .. } => {}
                        Token::Parent { id } => parents.push(id),
                        Token::Committer { signature } => {
                            commit_time = signature.seconds();
                            break;
                        }
                        _ => {}
                    }
                }
                (parents, commit_time, None)
            }
            gix::traverse::commit::Either::CachedCommit(commit) => {
                let cache = cache.expect("cached commits originate from the provided commit-graph");
                let mut parents = gix::traverse::commit::ParentIds::new();
                for parent in commit.iter_parents() {
                    let parent =
                        parent.map_err(|err| anyhow::anyhow!("could not decode commit-graph parent: {err}"))?;
                    parents.push(cache.id_at(parent).to_owned());
                }
                (
                    parents,
                    commit.committer_timestamp() as gix::date::SecondsSinceUnixEpoch,
                    Some(commit.generation()),
                )
            }
        };
        if shallow.contains(&id) {
            parents.clear();
        }
        let parents: Vec<_> = parents
            .into_iter()
            .map(|parent| self.intern(parent))
            .collect::<Result<_>>()?;
        let start: u32 = self
            .parents

View on GitHub (pinned to e73179060b)