{"record":{"id":"6b994a4635d271e3","repo":"GitoxideLabs/gitoxide","slug":"internal-nodes-have-no-object-id-key","errorCode":null,"errorMessage":"internal nodes have no object ID key","messagePattern":"internal nodes have no object ID key","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix-note/src/lib.rs","lineNumber":257,"sourceCode":"    ///\n    /// This performance-sensitive trie stores `prefix` and `prefix_len` separately so it can compare whole bytes\n    /// directly. Notes fanout advances by two hexadecimal digits at a time, so it does not need the nibble-granular\n    /// lengths supported by [`gix_hash::Prefix`].\n    prefix: ObjectId,\n    /// The number of leading bytes in `prefix` that are significant.\n    prefix_len: usize,\n    /// The slash-separated path from the notes root to this fanout subtree.\n    path: BString,\n    /// The object ID of the on-disk tree represented by this subtree.\n    tree_id: ObjectId,\n}\n\nimpl Node {\n    fn key(&self) -> &oid {\n        match self {\n            Node::Note(note) => &note.annotated_object_id,\n            Node::Subtree(subtree) => &subtree.prefix,\n            Node::Internal(_) => unreachable!(\"internal nodes have no object ID key\"),\n        }\n    }\n}\n\nimpl Subtree {\n    fn contains(&self, id: &oid) -> bool {\n        self.prefix.as_bytes()[..self.prefix_len] == id.as_bytes()[..self.prefix_len]\n    }\n}\n\nimpl InternalNode {\n    fn get(\n        &mut self,\n        annotated_object_id: &oid,\n        nibble: usize,\n        objects: &impl Find,\n        non_notes: &mut Vec<TreeEntry>,\n    ) -> Result<Option<ObjectId>, Error> {","sourceCodeStart":239,"sourceCodeEnd":275,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-note/src/lib.rs#L239-L275","documentation":"`Node::key()` in `gix-note` returns the identifier for a note or subtree node; `Internal` fanout nodes carry no object-ID key, so the arm panics. Hitting it means the note-tree traversal stored/encountered an internal node where only leaf-bearing nodes are expected.","triggerScenarios":"Only if the note tree in-memory structure invariants break (e.g. a bug in `insert`/`load_subtree` placing an `Internal` node where `key()` is called). Not triggered by note content or repository data alone.","commonSituations":"Should never occur; would surface while inserting or looking up notes in a fanout-style notes tree if traversal logic regressed.","solutions":["Report upstream with the notes tree shape (e.g. `git notes` output and fanout levels).","Update `gix-note` to the latest version.","If maintaining, make `key()` return `Option<&oid>` or an error for internal nodes."],"exampleFix":"// before\nNode::Internal(_) => unreachable!(\"internal nodes have no object ID key\"),\n// after\nNode::Internal(_) => return Err(message(\"internal note node has no key\"))","handlingStrategy":"try-catch","validationCode":"// Sanity-check the notes ref exists and looks like a note tree before use\n// repo.find_reference(\"refs/notes/commits\")?.peel_to_kind(gix_object::Kind::Tree)?;","typeGuard":null,"tryCatchPattern":"// Wrap note operations in catch_unwind or a subprocess if robustness matters.\nlet out = std::panic::catch_unwind(|| notes.get(note_id));","preventionTips":["Use current gix versions","Test with fanout notes trees (e.g. repositories with many notes)","Fuzz note insert/get/remove paths when contributing"],"tags":["rust","panic","internal-invariant","notes"],"backgroundTag":"internal-invariant-violation","analyzedSha":"e73179060badf27222d790981fac3f84c1830a7e","analyzedAt":"2026-09-08T11:26:50.865Z","contentChangedAt":"2026-09-08T11:26:50.865Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}