GitoxideLabs/gitoxide · warning

consumed bytes as set by cache

Error message

consumed bytes as set by cache

What it means

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.

Solutions

  1. Use the stock delta cache implementations which always record consumed input
  2. Verify any custom `gix_pack::cache::FromEntries` implementation sets kind and consumed_input when storing entries
  3. Update gix-pack and report a reproducible case upstream
Defensive patterns

Strategy: validation

Validate before calling

// ensure your custom cache always records consumed input when storing entries
// if let Some(entry) = my_cache.get(id, offset) { assert!(entry.consumed_input.is_some()); }

Try / catch

std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| pack.decode_entry(id, &mut out, ...))).is_err()

Prevention

When it happens

Trigger: 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.

Common situations: Seen only during gix-pack development, fuzzing, or if a custom `cache::FromEntries` implementation is supplied that fails to set consumed_input.

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/a4b7c2759e7a9097. Report an issue: GitHub.

Appendix: source

Thrown at gix-pack/src/data/file/decode/entry.rs:299

                    Some(ResolvedBase::InPack(entry)) => entry,
                    Some(ResolvedBase::OutOfPack { end, kind }) => {
                        base_buffer_size = Some(end);
                        object_kind = Some(kind);
                        break;
                    }
                    None => return Err(Error::DeltaBaseUnresolved(base_id)),
                },
                _ => unreachable!("cursor.is_delta() only allows deltas here"),
            };
        }

        // This can happen if the cache held the first entry itself
        // We will just treat it as an object then, even though it's technically incorrect.
        if chain.is_empty() {
            return Ok(Outcome::from_object_entry(
                object_kind.expect("object kind as set by cache"),
                &first_entry,
                consumed_input.expect("consumed bytes as set by cache"),
            ));
        }

        // First pass will decompress all delta data and keep it in our output buffer
        // [<possibly resolved base object>]<delta-1..delta-n>...
        // so that we can find the biggest result size.
        let total_delta_data_size: usize = total_delta_data_size.try_into().map_err(|_| Error::OutOfMemory)?;

        let chain_len = chain.len();
        let actual_base_size = match base_buffer_size {
            Some(size) => size,
            None => self.decoded_object_size(cursor.decompressed_size)?,
        };
        let (first_buffer_end, second_buffer_end) = {
            let delta_start = base_buffer_size.unwrap_or(0);

            let delta_range = Range {
                start: delta_start,

View on GitHub (pinned to e73179060b)