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
- Report upstream if observed, including the notes ref and operation (get/insert/remove).
- Upgrade `gix-note`.
- 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
- Keep gix-note versions consistent with gix-object
- Exercise notes trees with mixed fanout depths in tests
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
- internal nodes have no object ID key
- only value and unspecified are possible here
- parent-match assures this
- upper match already assured we only deal with blobs
- fixed size array with three items
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)