Skip to content

fix(oci): drop a moved tag from its previous manifest's tag set - #1469

Merged
TerryHowe merged 1 commit into
oras-project:mainfrom
ctxloop:fix/retag-stale-tag-set
Sep 27, 2026
Merged

TerryHowe merged 1 commit into
oras-project:mainfrom
ctxloop:fix/retag-stale-tag-set

Conversation

@ctxloop

@ctxloop ctxloop commented Sep 27, 2026

Copy link
Copy Markdown
Contributor

resolver.Memory.Tag overwrites a reference's forward entry but leaves the reference in the reverse tag set of the descriptor it previously pointed to. oci.Store.isTagged reads that tag set, so Delete with AutoGC keeps a manifest whose tag has moved to another manifest, along with the blobs only it references, until an explicit GC(). Details and reproduction are in #1468.

The fix removes the reference from its previous descriptor's tag set before adding it to the new one, and drops that set once it is empty, as Untag does. It runs under the same lock as the rest of Tag, so a concurrent Resolve sees the reference pointing to either the old or the new descriptor, never to neither.

Tests

Both tests fail on main @ a0f6556 without the fix and pass with it.

  • TestTagSetRetag (internal/resolver) moves latest from one descriptor to another and checks both tag sets:

    memory_test.go:140: TagSet(oldDesc) = map[latest:{}], want no "latest" after it moved to newDesc
    
  • TestStore_DeleteWithAutoGCAfterRetag (content/oci) pushes an index I referencing manifest A, moves latest from A to manifest B, and deletes I. Before the fix, A and its layer foo remain:

    oci_test.go:2494: {application/vnd.oci.image.layer.v1.tar sha256:2c26b46b... 3 [] map[] [] <nil> } should not exist in store
    oci_test.go:2494: {application/vnd.oci.image.manifest.v1+json sha256:da223c7d... 335 [] map[] [] <nil> } should not exist in store
    

    It also checks that B, its layer, the shared config and latest → B are left intact.

Dropping the emptied set is exercised by TestTagSetRetag but not asserted, since TagSet returns an empty set either way. It only keeps m.tags from accumulating empty entries.

make test passes, including -race.

v2

The reverse index dates from #680, so v2 has had the same bug since v2.4.0, and both tests fail the same way there. I can send a backport once this is merged.

Fixes #1468

resolver.Memory.Tag overwrote the forward index entry for a reference
but never removed the reference from the reverse index of the
descriptor it previously pointed to. oci.Store.isTagged reads that
reverse index, so Delete with AutoGC treated a manifest whose tag had
moved away as still tagged and left it, and the blobs only it
referenced, on disk until an explicit GC.

Remove the reference from its previous descriptor's tag set before
adding it to the new one, dropping the set once it is empty as Untag
does.

Fixes oras-project#1468

Signed-off-by: ctxloop <148732717+ctxloop@users.noreply.github.com>
@codecov

codecov Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.12%. Comparing base (a0f6556) to head (6c800bf).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1469      +/-   ##
==========================================
- Coverage   91.13%   91.12%   -0.01%     
==========================================
  Files          98       98              
  Lines        7614     7619       +5     
==========================================
+ Hits         6939     6943       +4     
- Misses        664      665       +1     
  Partials       11       11              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@TerryHowe TerryHowe left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm

@TerryHowe
TerryHowe merged commit c3de6f8 into oras-project:main Sep 27, 2026
10 checks passed
@ctxloop
ctxloop deleted the fix/retag-stale-tag-set branch September 28, 2026 00:17
TerryHowe pushed a commit that referenced this pull request Sep 28, 2026
… (#1475)

resolver.Memory.Tag overwrote the forward index entry for a reference
but never removed the reference from the reverse index of the
descriptor it previously pointed to. oci.Store.isTagged reads that
reverse index, so Delete with AutoGC treated a manifest whose tag had
moved away as still tagged and left it, and the blobs only it
referenced, on disk until an explicit GC.

Remove the reference from its previous descriptor's tag set before
adding it to the new one, dropping the set once it is empty as Untag
does.

Fixes #1468


(cherry picked from commit c3de6f8)

Signed-off-by: ctxloop <148732717+ctxloop@users.noreply.github.com>
@TerryHowe TerryHowe mentioned this pull request Oct 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

oci: moving a tag leaves it in the old manifest's tag set, so Delete with AutoGC keeps that manifest

2 participants