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
- Rewrite the reference name using the canonical lowercase full-length hex encoding of the commit ID
- Recompute the expected name with `reference(id)` instead of hand-building it
- 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
- Always build stash reference names with `stash::reference(id)`, never by hand
- Never abbreviate or uppercase hex IDs in reference names
- Treat tix stash refs as opaque; don't rename them in scripts
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
- rewritten commit already has saved worktree state
- already has saved worktree state
- could not inspect a stash reference before rebasing
- cannot drop stashed commit
- stashes at and would converge on
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)