GitoxideLabs/gitoxide · error

change ID is ambiguous in the Tix view; candidates

Error message

change ID {change_id} is ambiguous in the Tix view; candidates:
  {candidates}

What it means

`relocate_after_editor` expects the recorded change ID to resolve to exactly one commit in the Tix view. If the candidate list contains more than one commit, the target is ambiguous and relocation cannot proceed safely, so the error lists all matching candidate commits (short-displayed via `crate::change_id::display_short`).

Solutions

  1. Inspect the listed candidate commits and disambiguate manually (rebase/drop the duplicate) before retrying.
  2. Refresh or repair the Tix view so each change ID maps to exactly one commit, then retry the reword.
  3. Avoid operations that duplicate a change (e.g. cherry-picking a change ID already in the view) or resolve the duplication first.
  4. If duplicates are intentional, use a commit ID directly instead of relocating by change ID.
Defensive patterns

Strategy: validation

Validate before calling

let matches: Vec<_> = candidate_ids.into_iter().filter(|id| crate::change_id::for_commit(repo, *id).map_or(false, |c| c == change_id)).collect();
if matches.len() != 1 { /* resolve duplicates before relocating */ }

Try / catch

match relocate_after_editor(repo, change_id) {
    Ok(res) => res,
    Err(e) if e.to_string().contains("is ambiguous") => {
        // prompt user to pick among candidates printed in the error
        disambiguate(repo, change_id)?
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling `relocate_after_editor` (via `relocates_an_editor_reword_onto_a_concurrent_amend` or `editor_relocation_requires_one_visible_commit`) when `crate::change_id::for_commit` matches two or more commits in the candidate set — e.g. a change ID duplicated across rewritten/merged history.

Common situations: Duplicate change IDs after a history rewrite that kept two copies of the same logical change (e.g. cherry-pick or rebase duplicates); concurrent operations creating divergent commits sharing a change ID; corrupted or manually edited Tix metadata.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at gix-tix/src/edit/reword.rs:70

    change_id: gix::hash::ChangeId,
) -> Result<(crate::history::HistoryGraph, gix::ObjectId)> {
    let graph = super::loaded_view_graph_with_hidden(repo, revisions, hidden_revisions)?;
    let mut matches = Vec::new();
    for id in graph.stored_commit_ids() {
        if crate::change_id::for_commit(repo, id)? == change_id {
            matches.push(id);
        }
    }
    match matches.as_slice() {
        [target] => Ok((graph, *target)),
        [] => anyhow::bail!("change ID {change_id} is no longer present in the Tix view"),
        candidates => {
            let candidates = candidates
                .iter()
                .map(|id| crate::change_id::display_short(repo, *id))
                .collect::<Result<Vec<_>>>()?
                .join("\n  ");
            anyhow::bail!("change ID {change_id} is ambiguous in the Tix view; candidates:\n  {candidates}")
        }
    }
}

#[tracing::instrument(skip_all, fields(commit_id = %id))]
pub(crate) fn document(repo: &gix::Repository, id: gix::ObjectId) -> Result<(gix::command::Prepare, Vec<u8>)> {
    document_with_author(repo, id, None)
}

pub(crate) fn document_with_author(
    repo: &gix::Repository,
    id: gix::ObjectId,
    author: Option<&[u8]>,
) -> Result<(gix::command::Prepare, Vec<u8>)> {
    let editor = repo
        .editor_command()
        .context("could not prepare Git editor")?
        .context("no Git editor is available")?;

View on GitHub (pinned to e73179060b)