GitoxideLabs/gitoxide · error

the matching node was checked to be a subtree

Error message

the matching node was checked to be a subtree

What it means

In `gix-note`'s subtree-lookup path, the code double-checks with `matches!` that the child at the nibble index is a `Subtree`, then unwraps and re-checks with an `else unreachable!`. Firing means the same memory location changed between the check and the destructuring — an internal concurrency or aliasing violation.

Solutions

  1. Report upstream if observed, including the notes ref and operation (get/insert/remove).
  2. Upgrade `gix-note`.
  3. If maintaining, fold the check-and-destructure into one match to eliminate the double test.

Example fix

// before
let Node::Subtree(subtree) = *self.children[index].take().expect("...") else { unreachable!("...") };
// after
let Some(Node::Subtree(subtree)) = self.children[index].take() else { return Err(message("child disappeared during subtree load")) };
Defensive patterns

Strategy: try-catch

Try / catch

// Internal double-check; isolate note lookups via catch_unwind.

Prevention

When it happens

Trigger: Not reachable in single-threaded traversal; would only fire if child-slot mutation logic (e.g. `take()` placement or `load_subtree` side effects) changed so the node is no longer a `Subtree` when matched.

Common situations: Practically never; appears only if notes-tree loading code is refactored incorrectly.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at gix-note/src/lib.rs:286

impl InternalNode {
    fn get(
        &mut self,
        annotated_object_id: &oid,
        nibble: usize,
        objects: &impl Find,
        non_notes: &mut Vec<TreeEntry>,
    ) -> Result<Option<ObjectId>, Error> {
        if self.load_matching_subtree(annotated_object_id, nibble, objects, non_notes)? {
            return self.get(annotated_object_id, nibble, objects, non_notes);
        }

        let index = nibble_at(annotated_object_id, nibble);
        let should_load = self.children[index]
            .as_deref()
            .is_some_and(|node| matches!(node, Node::Subtree(subtree) if subtree.contains(annotated_object_id)));
        if should_load {
            let Node::Subtree(subtree) = *self.children[index].take().expect("the matching subtree is present") else {
                unreachable!("the matching node was checked to be a subtree")
            };
            load_subtree(subtree, self, nibble, objects, non_notes)?;
            return self.get(annotated_object_id, nibble, objects, non_notes);
        }

        match self.children[index].as_deref_mut() {
            Some(Node::Internal(child)) => child.get(annotated_object_id, nibble + 1, objects, non_notes),
            Some(Node::Note(note)) if note.annotated_object_id == annotated_object_id => Ok(Some(note.note_blob_id)),
            _ => Ok(None),
        }
    }

    fn insert(
        &mut self,
        entry: Node,
        nibble: usize,
        objects: &impl Find,
        non_notes: &mut Vec<TreeEntry>,

View on GitHub (pinned to e73179060b)