GitoxideLabs/gitoxide · warning

object kind as set by cache

Error message

object kind as set by cache

What it means

A `expect()` panic in `resolve_deltas`. When the delta chain is empty because the delta cache already held the first entry itself, the code treats the cached entry as a full object and expects `object_kind` to have been recorded by the cache. If `object_kind` is `None` here, the cache returned a chain result without a kind, violating the cache contract — a bug in gix-pack's delta cache interaction.

Solutions

  1. Update gix-pack; the cache is documented to always set the object kind
  2. Disable or reconfigure the delta-from-state cache as a workaround (e.g. use `cache::Never`)
  3. Reproduce with the pack file and report upstream
Defensive patterns

Strategy: try-catch

Try / catch

let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| pack.decode_entry(id, &mut out, ...)));
if result.is_err() { eprintln!("internal panic in delta resolution"); }

Prevention

When it happens

Trigger: Calling `decode_entry` (which routes to `resolve_deltas`) on a delta entry whose base object is served entirely from a delta cache that stored the first entry's header and data but (hypothetically) not its kind.

Common situations: Heavy delta-cache usage (e.g. `gix_pack::cache::delta` enabled) while decoding many pack entries; library development or fuzzing of the cache/decoder boundary.

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

Appendix: source

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

                }
                Header::RefDelta { base_id } => match resolve(base_id.as_ref(), out) {
                    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);

View on GitHub (pinned to e73179060b)