gix cat: Encountered unsupported command code: 0
Current behavior π―
I encountered this after updating jj, but I bisected it to jj's update from gix 0.73.0 to 0.74.1, and bisected further to https://github.com/GitoxideLabs/gitoxide/pull/2155
In a large repo I have, reading a certain object always fails.
$ gix cat 70b81af0814b0620db4af251b3f0e9530457104b
Error: An error occurred while obtaining an object from the packed object store
Caused by:
Encountered unsupported command code: 0
Stack backtrace:
0: anyhow::error::<impl core::convert::From<E> for anyhow::Error>::from
1: <core::result::Result<T,F> as core::ops::try_trait::FromResidual<core::result::Result<core::convert::Infallible,E>>>::from_residual
2: gitoxide_core::repository::cat::display_object
3: gitoxide_core::repository::cat::function::cat
4: gitoxide::plumbing::main::main::{{closure}}
5: gitoxide::shared::pretty::prepare_and_run
6: gitoxide::plumbing::main::main
7: gix::main
8: core::ops::function::FnOnce::call_once
9: std::sys::backtrace::__rust_begin_short_backtrace
10: std::rt::lang_start::{{closure}}
11: std::rt::lang_start_internal
12: std::rt::lang_start
13: main
14: __libc_start_call_main
15: __libc_start_main_alias_1
16: _start
Expected behavior π€
The object should be readable, as it is with gix 0.73.0.
Git behavior
git cat-file -p 70b81af0814b0620db4af251b3f0e9530457104b in the same repo outputs the contents of a file in the repo.
Steps to reproduce πΉ
Unfortunately, I can only narrow this down to a 5.1GiB pack file, and I don't know how this problem was created. It seems like the pack file is valid, given that both git and earlier versions of gix read it fine. Copying the .pack file to a new repo and regenerating the .idx and .rev with git-index-pack preserves the problem, so the problem is in the .pack. Is there a way I can narrow down the pack file to just the problematic object?
Thanks for reporting and the initial investigation, and sorry for the hassle.
I understand this issue isn't present before #2155 was merged, and that it's unrelated to the pack index which was also regenerated. Thus, let me CC @folkertdev.
Besides that, I am pretty sure this is at least related to #2044, which was left open as well, with this comment being quite indicative of what seems to happen.
So it really seems to be something about zlib-rs and/or how these pack entries have been created. It could also be related to how gitoxide handles its buffers, but from all I could tell it just creates buffers of the required size and tells it to inflate everything into it in one go.
Hmm, could this be some sort of overflow bug, given the large inputs?
Otherwise I'd need some sort of specific sequence of inputs to zlib to say anything useful.
If you could help me figure out how to extract the problematic part of the pack file I'd be happy to share that.
Assuming
- the problem here would be in decompression
- gixoxide only makes one
Decompressand reuses it
I'd just need the sequence of inputs to decompress here
https://github.com/GitoxideLabs/gitoxide/blob/df393f37bb03955eff3ad52c27440393737fb334/gix-features/src/zlib/mod.rs#L47
Something like this patch
https://github.com/GitoxideLabs/gitoxide/compare/main...folkertdev:gitoxide:store-zlib-decompression
Hopefully that creates a smaller file, and we can replay that using zlib-rs and zlib-ng and see if there are any differences. If the file is still too large, maybe only keep data after the most recent reset and we can see if that is sufficient.
Reading over the other issue again, you could also try and see if disabling the max-performance feature helps at all.
Thanks so much for chiming in @folkertdev!
As a note, I think disabling max-performance shouldn't make a difference (anymore) as by now we always use zlib-rs, and we always use a hardened SHA1 implementation. There is no alternative options anymore. In any case, it's probably easy enough to try it anyway.
Just one more question: in #2044 we already did an investigation and it turned out to be an x86 specific issue that doesn't happen on AARM64 for instance. @ElvishJerricco do you happen to be on x86? Because if it's AARM or ARM, we'd have a first encounter.
Just another data point. We received this bug report: https://github.com/jj-vcs/jj/issues/8350
The cloned "firefox" repository has large pack files:
% ll .git/objects/pack
total 4.7G
-r--r--r-- 1 yuya yuya 409M 2025-12-20 13:03 pack-5a5df80c4c69363dd6caa84400c16e35457f0b45.idx
-r--r--r-- 1 yuya yuya 4.3G 2025-12-20 13:03 pack-5a5df80c4c69363dd6caa84400c16e35457f0b45.pack
-r--r--r-- 1 yuya yuya 48M 2025-12-20 13:03 pack-5a5df80c4c69363dd6caa84400c16e35457f0b45.rev
and the current gix cannot read some objects (whereas gix 0.73.0 can):
% gix cat e3e80c740c34c84c75710f36e7a0dc97c3bdede2 | xxd
00000000: 7400 0000 0000 0000 0000 0000 0000 0000 t...............
00000010: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000030: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000040: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000060: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000070: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000080: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000090: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000100: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000110: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000120: 0000 0000 00 .....
In jj git init's workload, it seems that some misplaced or uninitialized data was loaded:
[lib/src/repo.rs:1643:37] (commit.id(), commit.store_commit()) = (
CommitId(
"1ca754d72fdadc1cea6358b852440296d594f9c1",
),
Commit {
parents: [
CommitId(
"e3e80c740c34c84c75710f36e7a0dc97c3bdede2",
),
],
predecessors: [],
root_tree: Resolved(
TreeId(
"cbcda8d64976771cbea0a1afd86d2fca2ed4d905",
),
),
conflict_labels: Resolved(
"",
),
change_id: ChangeId(
"839f29ab6940224a1d1ac657383b5bf4",
),
description: "Bug 596905 - Unconventional date format in the new about window (about box). r=gavin a=blocking2.0",
author: Signature {
name: "Margaret Leibovic",
email: "[email protected]",
timestamp: Timestamp {
timestamp: MillisSinceEpoch(
1285090298000,
),
tz_offset: -420,
},
},
committer: Signature {
name: "Margaret Leibovic",
email: "[email protected]",
timestamp: Timestamp {
timestamp: MillisSinceEpoch(
1285090298000,
),
tz_offset: -420,
},
},
secure_sig: None,
},
)
[lib/src/repo.rs:1643:37] (commit.id(), commit.store_commit()) = (
CommitId(
"e3e80c740c34c84c75710f36e7a0dc97c3bdede2",
),
Commit {
parents: [
CommitId(
"e3e80c740c34c84c75710f36e7a0dc97c3bdede2",
),
],
predecessors: [],
root_tree: Resolved(
TreeId(
"cbcda8d64976771cbea0a1afd86d2fca2ed4d905",
),
),
conflict_labels: Resolved(
"",
),
change_id: ChangeId(
"47b7bdc3e93b05e76cf08eae32132c30",
),
description: "Bug 596905 - Unconventional date format in the new ",
author: Signature {
name: "Margaret Leibovic",
email: "[email protected]",
timestamp: Timestamp {
timestamp: MillisSinceEpoch(
1285090298000,
),
tz_offset: -420,
},
},
committer: Signature {
name: "Margaret Leibovic",
email: "[email protected]",
timestamp: Timestamp {
timestamp: MillisSinceEpoch(
1285090298000,
),
tz_offset: -420,
},
},
secure_sig: None,
},
)
The commit object 1ca754d72fdadc1cea6358b852440296d594f9c1 is good, but it seems e3e80c740c34c84c75710f36e7a0dc97c3bdede2 copy the previous data?
The problem is that decompress() truncates input length to c_uint. If the input slice slightly exceeds u32 range, there would be very few data to consume. Therefore, decompress() ends with Status::Ok (meaning more input may be needed.)
https://github.com/trifectatechfoundation/zlib-rs/blob/v0.5.4/zlib-rs/src/stable.rs#L119
This hack fixed the problem at my end.
diff --git a/gix-features/src/zlib/mod.rs b/gix-features/src/zlib/mod.rs
index 448b8f1aaa..c32665722c 100644
--- a/gix-features/src/zlib/mod.rs
+++ b/gix-features/src/zlib/mod.rs
@@ -44,6 +44,7 @@
FlushDecompress::Finish => zlib_rs::InflateFlush::Finish,
};
+ let input = input.get(..std::ffi::c_uint::MAX as usize).unwrap_or(input);
let status = self.0.decompress(input, output, inflate_flush)?;
match status {
zlib_rs::Status::Ok => Ok(Status::Ok),
@yuja May I ask another datapoint? Since you seem to be able to reproduce the issue, which once again I am not on macOS/AARM64, it would be really valuable if you could try it again with the latest main of gitoxide. It uses zlib-rs directly, without C interop.
Maybe that changes things. If it doesn't, it's clear what needs to be done, and we have to bring back zlib-ng which presumably was the backend JJ used before. If not, miniz_oxide is also an option, and even a more natural (albeit slower) one. I'd love to sort this out this year, and simply bringing back the backend option seems like the way to go until this is properly fixed.
My 'firefox' results for posterity
β― gix clone https://github.com/mozilla-firefox/firefox.git
10:18:20 indexing done 12.5M objects in 134.22s (92.8K objects/s)
10:18:20 decompressing done 10.1GB in 134.22s (75.4MB/s)
10:18:53 Resolving done 12.5M objects in 32.48s (383.3K objects/s)
10:18:53 Decoding done 271.9GB in 32.48s (8.4GB/s)
10:18:54 writing index file done 427.9MB in 0.90s (473.5MB/s)
10:18:54 create index file done 12.5M objects in 168.50s (73.9K objects/s)
10:18:54 read pack done 4.5GB in 169.55s (26.7MB/s)
10:19:23 checkout done 398.4K files in 27.95s (14.3K files/s)
10:19:23 writing done 3.2GB in 27.95s (116.3MB/s)
[..]
> cd firefox
firefox (ξ main) via π
Ά v9.2.1 via β via ξ via π
β― gix cat e3e80c740c34c84c75710f36e7a0dc97c3bdede2
tree 76b3c0e4cdd474c9e2eec913f183a0966ec572dc
parent a88a29bf8eb19169aed6ddb020407bcdbd326fb2
author Oleg Romashin <[email protected]> 1285089018 -0700
committer Oleg Romashin <[email protected]> 1285089018 -0700
Bug 598250 - De-Initialize basic osso_context for Maemo5 Qt port. r=dougt a=npodb%
firefox (ξ main) via π
Ά v9.2.1 via β via ξ via π
β― gix cat e3e80c740c34c84c75710f36e7a0dc97c3bdede2 | hexyl
ββββββββββ¬ββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββ¬βββββββββ¬βββββββββ
β00000000β 74 72 65 65 20 37 36 62 β 33 63 30 65 34 63 64 64 βtree 76bβ3c0e4cddβ
β00000010β 34 37 34 63 39 65 32 65 β 65 63 39 31 33 66 31 38 β474c9e2eβec913f18β
β00000020β 33 61 30 39 36 36 65 63 β 35 37 32 64 63 0a 70 61 β3a0966ecβ572dc_paβ
β00000030β 72 65 6e 74 20 61 38 38 β 61 32 39 62 66 38 65 62 βrent a88βa29bf8ebβ
β00000040β 31 39 31 36 39 61 65 64 β 36 64 64 62 30 32 30 34 β19169aedβ6ddb0204β
β00000050β 30 37 62 63 64 62 64 33 β 32 36 66 62 32 0a 61 75 β07bcdbd3β26fb2_auβ
β00000060β 74 68 6f 72 20 4f 6c 65 β 67 20 52 6f 6d 61 73 68 βthor Oleβg Romashβ
β00000070β 69 6e 20 3c 72 6f 6d 61 β 78 61 40 67 6d 61 69 6c βin <romaβxa@gmailβ
β00000080β 2e 63 6f 6d 3e 20 31 32 β 38 35 30 38 39 30 31 38 β.com> 12β85089018β
β00000090β 20 2d 30 37 30 30 0a 63 β 6f 6d 6d 69 74 74 65 72 β -0700_cβommitterβ
β000000a0β 20 4f 6c 65 67 20 52 6f β 6d 61 73 68 69 6e 20 3c β Oleg Roβmashin <β
β000000b0β 72 6f 6d 61 78 61 40 67 β 6d 61 69 6c 2e 63 6f 6d βromaxa@gβmail.comβ
β000000c0β 3e 20 31 32 38 35 30 38 β 39 30 31 38 20 2d 30 37 β> 128508β9018 -07β
β000000d0β 30 30 0a 0a 42 75 67 20 β 35 39 38 32 35 30 20 2d β00__Bug β598250 -β
β000000e0β 20 44 65 2d 49 6e 69 74 β 69 61 6c 69 7a 65 20 62 β De-Initβialize bβ
β000000f0β 61 73 69 63 20 6f 73 73 β 6f 5f 63 6f 6e 74 65 78 βasic ossβo_contexβ
β00000100β 74 20 66 6f 72 20 4d 61 β 65 6d 6f 35 20 51 74 20 βt for Maβemo5 Qt β
β00000110β 70 6f 72 74 2e 20 72 3d β 64 6f 75 67 74 20 61 3d βport. r=βdougt a=β
β00000120β 6e 70 6f 64 62 β βnpodb β β
ββββββββββ΄ββββββββββββββββββββββββββ΄ββββββββββββββββββββββββββ΄βββββββββ΄ββββββββ
That was racy :D, @yuja. And it looks like you already used the latest version.
@folkertdev The problem seems to be that gitoxide can totally pass in inputs that are larger than u32, they are uncapped as it's unclear how many bytes of the stream will be consumed. It looks like this is something that could classify as bug in zlib-rs, what do you think?
Now I am optimistic that there won't be a need to bring back zlib-ng π, exciting!
That does seem very plausible, good investigative work! It might be a couple of days before I'll be able to look into this further.
I think https://github.com/trifectatechfoundation/zlib-rs/pull/449 should fix it, could someone check that?
I think trifectatechfoundation/zlib-rs#449 should fix it, could someone check that?
It fixed the problem of jj, thanks.