GitoxideLabs/gitoxide · warning

always valid for non-refs

Error message

always valid for non-refs

What it means

A `expect()` panic in the public pack entry header decoding (`Outcome` construction). The loop returns an `Outcome` only for non-delta headers (Tree/Blob/Commit/Tag), and `as_kind()` is `Some` exactly for those, so the conversion cannot fail. Failure would mean a delta header reached a non-delta branch.

Solutions

  1. Keep all gix-* crates at the same version (use the workspace-provided version pins)
  2. Update gix-pack if a header-decoding fix was released
  3. Report a bug with the offending pack bytes
Defensive patterns

Strategy: try-catch

Try / catch

let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| header.to_outcome()));
if r.is_err() { eprintln!("pack header invariant violated"); }

Prevention

When it happens

Trigger: Calling pack entry header decoding (e.g. `gix_pack::data::entry::Header` Outcome computation) — the panic fires only if header matching logic regresses, e.g. after a version change or code modification.

Common situations: gix-pack development, fuzzing of pack entry headers with mutated bytes, or mismatched gix crate versions.

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

Appendix: source

Thrown at gix-pack/src/data/file/decode/header.rs:66

    /// For delta entries, this only probes the initial delta header bytes to determine the result
    /// object size. It can reject streams that end or overflow within that probe, but it does not
    /// fully validate that the compressed stream produces exactly the decompressed size declared in
    /// the pack entry header. Use [`File::decode_entry()`][crate::data::File::decode_entry()] when
    /// callers need that full validation.
    pub fn decode_header(
        &self,
        mut entry: data::Entry,
        inflate: &mut gix_zlib::Inflate,
        resolve: &dyn Fn(&gix_hash::oid) -> Option<ResolvedBase>,
    ) -> Result<Outcome, Error> {
        use crate::data::entry::Header::*;
        let mut num_deltas = 0;
        let mut first_delta_decompressed_size = None::<u64>;
        loop {
            match entry.header {
                Tree | Blob | Commit | Tag => {
                    return Ok(Outcome {
                        kind: entry.header.as_kind().expect("always valid for non-refs"),
                        object_size: first_delta_decompressed_size.unwrap_or(entry.decompressed_size),
                        num_deltas,
                    });
                }
                OfsDelta { base_distance } => {
                    num_deltas += 1;
                    if first_delta_decompressed_size.is_none() {
                        first_delta_decompressed_size = Some(self.decode_delta_object_size(inflate, &entry)?);
                    }
                    entry = self.entry(entry.checked_base_pack_offset(base_distance).ok_or(
                        crate::data::entry::decode::Error::Corrupt {
                            message: "an ofs-delta base distance pointing before pack start",
                        },
                    )?)?;
                }
                RefDelta { base_id } => {
                    num_deltas += 1;
                    if first_delta_decompressed_size.is_none() {

View on GitHub (pinned to e73179060b)