Forward-port #1095 to main: graph.Memory should use digest as map key (GC can prune shared blobs)
#1,258 opened on Jul 30, 2026
Repository metrics
- Stars
- (270 stars)
- PR merge metrics
- (PR metrics pending)
Description
Description
The garbage-collection correctness bug fixed by #1095 (which resolved #1093) on the v2 branch is still present on main (v3) and needs to be forward-ported.
In internal/graph/memory.go, the in-memory graph keys its nodes, predecessors, and successors maps on descriptor.Descriptor:
nodes map[descriptor.Descriptor]ocispec.Descriptor
predecessors map[descriptor.Descriptor]set.Set[descriptor.Descriptor]
successors map[descriptor.Descriptor]set.Set[descriptor.Descriptor]
Because internal/descriptor.Descriptor includes MediaType and Size in addition to Digest, two manifests that reference the same blob digest with a different media type are treated as distinct graph nodes. The underlying CAS store on disk is keyed purely by digest, so when one manifest is deleted, garbage collection misidentifies the shared blob as dangling and prunes it — invalidating the other manifest.
This is the same defect described in #1093 (reproducer there uses KitOps artifacts sharing a blob digest under different media types).
Fix
Port #1095 to main: key the graph maps on digest.Digest instead of descriptor.Descriptor, matching what the on-disk CAS uses.
nodes map[digest.Digest]ocispec.Descriptor
predecessors map[digest.Digest]set.Set[digest.Digest]
successors map[digest.Digest]set.Set[digest.Digest]
References
- v2 fix: #1095 (merged into
v2, released in v2.6.1 / v2.6.2) - Original bug report: #1093
- Affected file:
internal/graph/memory.go
Status on branches
| branch | map key | fixed? |
|---|---|---|
v2 |
digest.Digest |
✅ (via #1095) |
main (v3) |
descriptor.Descriptor |
❌ not yet ported |