GitoxideLabs/gitoxide · error

the undo queue records itself

Error message

the undo queue records itself

What it means

The undo metadata must never contain an entry for the undo queue ref itself; the queue records changes to other references only. `parse_config` rejects any `[ref "..."]` section whose reference name is the queue ref (as detected by `is_queue_ref`), preventing recursive/self-referential undo state that would corrupt undo/redo semantics.

Solutions

  1. Remove the `[ref "..."]` section whose name matches the undo queue ref from the metadata
  2. Re-record the undo state by re-running the tix operation so it is regenerated correctly
  3. Verify no script or hook writes changes to the queue ref into undo metadata
  4. Update to a tix version that filters the queue ref when serializing

Example fix

// before
[undo]
	version = v1
[ref "refs/tix/undo/queue"]
	before = missing
	after = object:def456
// after (queue section removed)
[undo]
	version = v1
[ref "refs/heads/main"]
	before = missing
	after = object:abc123
Defensive patterns

Strategy: validation

Validate before calling

// detect a self-referential queue entry before parsing
let queue_ref = "refs/tix/undo/queue";
let config = gix::config::File::try_from(body.as_ref())?;
for s in config.sections() {
    if s.header().name() == b"ref" {
        let name = s.header().subsection_name().unwrap_or_default();
        assert!(name != queue_ref, "undo queue records itself");
    }
}

Type guard

fn is_queue_ref(name: &bstr::BStr) -> bool {
    name.starts_with(b"refs/tix/undo/")
}

Try / catch

match parse_undo_metadata(&repo, &body) {
    Ok(changes) => changes,
    Err(e) if e.to_string().contains("records itself") => {
        eprintln!("self-referential undo state; rebuilding");
        rebuild_undo_state(&repo)?
    }
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Parsing undo metadata that contains a section like `[ref "refs/tix/undo/queue"]` — possible if a ref change targeting the queue was serialized by a buggy or tampered-with writer, or if the queue ref was renamed/repointed manually.

Common situations: Hand-editing the undo metadata, custom scripts writing tix refs, or an older/patched tix build that failed to exclude the queue ref when recording changes.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at gix-tix/src/edit/undo.rs:621

    ensure_exact_keys(&undo, &["version"])?;
    ensure!(
        undo.value("version").as_ref().map(|value| value.as_slice()) == Some(VERSION.as_bytes()),
        "unsupported undo metadata version"
    );

    let mut changes = Vec::new();
    let mut previous_name: Option<FullName> = None;
    for section in sections {
        ensure!(
            section.header().name() == b"ref",
            "undo metadata contains an unknown section"
        );
        let subsection = section
            .header()
            .subsection_name()
            .context("an undo ref section has no reference name")?;
        let name = FullName::try_from(subsection).context("an undo entry contains an invalid reference name")?;
        ensure!(!is_queue_ref(name.as_bstr()), "the undo queue records itself");
        if let Some(previous) = &previous_name {
            ensure!(
                previous < &name,
                "undo reference sections are duplicated or out of order"
            );
        }
        previous_name = Some(name.clone());
        ensure_exact_keys(&section, &["before", "after"])?;
        let before = section.value("before").context("an undo ref has no before-state")?;
        let before = parse_state(repo, before.as_bstr())?;
        let after = section.value("after").context("an undo ref has no after-state")?;
        let after = parse_state(repo, after.as_bstr())?;
        ensure!(before != after, "an undo ref does not change");
        changes.push(RefChange { name, before, after });
    }
    Ok(changes)
}

View on GitHub (pinned to e73179060b)