GitoxideLabs/gitoxide · error

tix stash reference does not use a canonical full commit ID

Error message

tix stash reference does not use a canonical full commit ID

What it means

Tix saves worktree state under synthetic references named after a stash prefix plus a full commit ID. This error fires when the text after the prefix does not parse back to a hex ID that re-serializes byte-for-byte identically — i.e. the ID is not written in canonical lowercase full-length form. `associated_commit` parses the suffix and compares `id.to_hex()` against the raw suffix bytes.

Solutions

  1. Rewrite the reference name using the canonical lowercase full-length hex encoding of the commit ID
  2. Recompute the expected name with `reference(id)` instead of hand-building it
  3. Delete or rename the malformed stash reference and re-create it via the tix stash command

Example fix

// before
let name = format!("{STASH_PREFIX}aBc123");
// after
let id = ObjectId::from_hex(b"abc123...")?;
let name = reference(id)?; // canonical full hex form
Defensive patterns

Strategy: validation

Validate before calling

fn is_canonical_stash_name(name: &BStr) -> bool {
    match name.strip_prefix(tix::history::STASH_PREFIX) {
        Some(suffix) => match ObjectId::from_hex(suffix) {
            Ok(id) => id.to_hex().to_string().as_bytes() == suffix,
            Err(_) => false,
        },
        None => false,
    }
}

Type guard

fn canonical_stash_id(name: &BStr) -> Option<ObjectId> {
    let suffix = name.strip_prefix(tix::history::STASH_PREFIX)?;
    let id = ObjectId::from_hex(suffix).ok()?;
    (id.to_hex().to_string().as_bytes() == suffix).then_some(id)
}

Try / catch

match stash::associated_commit(name) {
    Ok(Some(id)) => use_stash(id),
    Ok(None) => {} /* not a tix stash ref */,
    Err(e) if e.to_string().contains("canonical full commit ID") => repair_reference_name(name),
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling `edit::stash::associated_commit` with a reference name whose suffix is uppercase hex, abbreviated (short) hex, contains leading zeros removed, or is otherwise not exactly 40/64 lowercase hex characters after `STASH_PREFIX`.

Common situations: Hand-renaming or scripting stash references with shortened IDs; copying IDs from a UI that uppercases hex; older tix versions or external tools writing differently formatted stash refs.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


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

Appendix: source

Thrown at gix-tix/src/edit/stash.rs:36

use crate::open_repository;

pub(crate) fn reference(id: ObjectId) -> Result<gix::refs::FullName> {
    format!(
        "{}{}",
        String::from_utf8_lossy(crate::history::STASH_PREFIX),
        id.to_hex()
    )
    .try_into()
    .context("generated an invalid tix stash reference")
}

pub(crate) fn associated_commit(name: &BStr) -> Result<Option<ObjectId>> {
    let Some(suffix) = name.strip_prefix(crate::history::STASH_PREFIX) else {
        return Ok(None);
    };
    let id = ObjectId::from_hex(suffix).context("tix stash reference has an invalid commit ID")?;
    if id.to_hex().to_string().as_bytes() != suffix {
        anyhow::bail!("tix stash reference does not use a canonical full commit ID");
    }
    Ok(Some(id))
}

pub(super) struct RewriteEdits {
    pub forward: Vec<RefEdit>,
    pub rollback: Vec<RefEdit>,
}

pub(super) fn rewrite_edits(
    repo: &gix::Repository,
    rewritten: &HashMap<ObjectId, Option<ObjectId>>,
    removed: &HashSet<ObjectId>,
) -> Result<RewriteEdits> {
    let mut moves = Vec::new();
    let mut destinations = HashMap::<ObjectId, ObjectId>::new();
    for reference in repo.references()?.all()? {
        let reference = match reference {

View on GitHub (pinned to e73179060b)