{"record":{"id":"a4b7c2759e7a9097","repo":"GitoxideLabs/gitoxide","slug":"consumed-bytes-as-set-by-cache","errorCode":null,"errorMessage":"consumed bytes as set by cache","messagePattern":"consumed bytes as set by cache","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"gix-pack/src/data/file/decode/entry.rs","lineNumber":299,"sourceCode":"                    Some(ResolvedBase::InPack(entry)) => entry,\n                    Some(ResolvedBase::OutOfPack { end, kind }) => {\n                        base_buffer_size = Some(end);\n                        object_kind = Some(kind);\n                        break;\n                    }\n                    None => return Err(Error::DeltaBaseUnresolved(base_id)),\n                },\n                _ => unreachable!(\"cursor.is_delta() only allows deltas here\"),\n            };\n        }\n\n        // This can happen if the cache held the first entry itself\n        // We will just treat it as an object then, even though it's technically incorrect.\n        if chain.is_empty() {\n            return Ok(Outcome::from_object_entry(\n                object_kind.expect(\"object kind as set by cache\"),\n                &first_entry,\n                consumed_input.expect(\"consumed bytes as set by cache\"),\n            ));\n        }\n\n        // First pass will decompress all delta data and keep it in our output buffer\n        // [<possibly resolved base object>]<delta-1..delta-n>...\n        // so that we can find the biggest result size.\n        let total_delta_data_size: usize = total_delta_data_size.try_into().map_err(|_| Error::OutOfMemory)?;\n\n        let chain_len = chain.len();\n        let actual_base_size = match base_buffer_size {\n            Some(size) => size,\n            None => self.decoded_object_size(cursor.decompressed_size)?,\n        };\n        let (first_buffer_end, second_buffer_end) = {\n            let delta_start = base_buffer_size.unwrap_or(0);\n\n            let delta_range = Range {\n                start: delta_start,","sourceCodeStart":281,"sourceCodeEnd":317,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-pack/src/data/file/decode/entry.rs#L281-L317","documentation":"A `expect()` panic in `resolve_deltas` for `consumed_input`. In the cache-hit path (empty delta chain), the number of input bytes consumed by the cached entry must have been recorded by the cache along with the entry. `None` means the cache stored data without consumption metadata, breaking the cache/decoder contract.","triggerScenarios":"Same as the sibling `object_kind` expect: `decode_entry` on a delta entry fully served by a delta cache that kept the first entry but allegedly did not record consumed input bytes.","commonSituations":"Seen only during gix-pack development, fuzzing, or if a custom `cache::FromEntries` implementation is supplied that fails to set consumed_input.","solutions":["Use the stock delta cache implementations which always record consumed input","Verify any custom `gix_pack::cache::FromEntries` implementation sets kind and consumed_input when storing entries","Update gix-pack and report a reproducible case upstream"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// ensure your custom cache always records consumed input when storing entries\n// if let Some(entry) = my_cache.get(id, offset) { assert!(entry.consumed_input.is_some()); }","typeGuard":null,"tryCatchPattern":"std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| pack.decode_entry(id, &mut out, ...))).is_err()","preventionTips":["Prefer built-in cache types over custom ones","Test any custom cache against gix-pack's test suite","Update gix-pack regularly"],"tags":["rust","panic","internal-invariant","gix-pack","delta-cache"],"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"}