Skip to content

fix: attribute recursive copy tag failures to the destination - #2157

Merged
TerryHowe merged 1 commit into
oras-project:mainfrom
MaxFreedomPollard:fix-recursive-copy-tag-origin
Sep 6, 2026
Merged

TerryHowe merged 1 commit into
oras-project:mainfrom
MaxFreedomPollard:fix-recursive-copy-tag-origin

Conversation

@MaxFreedomPollard

Copy link
Copy Markdown
Contributor

What this PR does / why we need it:

oras cp -r blames the source registry when it is the destination that failed. If the final tagging of the root fails, the user sees Error response from registry: not found, with no host and nothing saying which side answered, so the source registry looks broken.

The cause is in recursiveCopy (cmd/oras/root/cp.go:279), which tags the root with a bare dst.Tag call and returns the error unwrapped. That error is not an oras.CopyError, so BinaryTarget.ModifyError (cmd/oras/internal/option/binary_target.go:73) has no origin to read and falls through to the generic handler, which asks the source target first. The source then claims the error and sets the plain registry prefix.

oras-go wraps exactly the same root tagging in a destination CopyError inside prepareCopy (oras-go v2.6.2, copy.go:480 and copy.go:513), which is why a plain oras cp gets the attribution right and only -r does not. This change wraps the tag error the same way, which is all BinaryTarget.ModifyError needs.

Before: Error response from registry: not found

After: Error from destination registry for "localhost:5000/gitlab-workhorse-ee:v19.3.0": not found

This PR is limited to the copy path. The second change suggested in the issue, adding a host check to the errdef.ErrNotFound branch of Target.ModifyError, touches every command that uses a remote target and is left out of this one.

Which issue(s) this PR fixes (optional, in fixes #<issue number>(, fixes #<issue_number>, ...) format, will close the issue(s) when PR gets merged):
Fixes #2156

Please check the following list:

  • Does the affected code have corresponding tests, e.g. unit test, E2E test?
  • Does this change require a documentation update?
  • Does this introduce breaking changes that would require an announcement or bumping the major version?
  • Do all new files have an appropriate license header?

Local validation (macOS arm64, Go 1.26.4):

go test -run Test_recursiveCopy_tagFailure ./cmd/oras/root/ on unmodified main: FAIL cp_test.go:592: recursiveCopy() error = not found, want *oras.CopyError. With the change: ok oras.land/oras/cmd/oras/root 0.266s.

go test ./cmd/oras/... ./internal/...: all packages ok.

golangci-lint run ./...: 0 issues.

go build ./cmd/oras: ok.

E2E was not run locally since it needs a registry and Docker. No E2E case asserts the message for this path; the only cp case that checks a destination prefix is the not-logged-in one in test/e2e/suite/command/cp.go:130, which already expects Error from destination registry for.

When `oras cp -r` finishes copying the graph, recursiveCopy tags the root
with a bare `dst.Tag` call in cmd/oras/root/cp.go. A failure there is
returned unwrapped, so it is not an `oras.CopyError` and
`BinaryTarget.ModifyError` cannot recover which side failed. The generic
handler is used instead, and because it consults the source target first,
a destination failure is printed as `Error response from registry: not
found` with no indication that the local destination answered.

`oras.Copy` already wraps the same root tagging in a destination
`CopyError` (oras-go v2 copy.go, prepareCopy), so a plain `oras cp` gets
the right attribution and only `-r` does not.

Wrap the tag error the same way. The message becomes `Error from
destination registry for "localhost:5000/repo:v1": not found`.

Signed-off-by: Max Freedom Pollard <272618364+MaxFreedomPollard@users.noreply.github.com>

@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

@codecov

codecov Bot commented Sep 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.63%. Comparing base (c75e8f3) to head (776e9ce).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #2157   +/-   ##
=======================================
  Coverage   87.62%   87.63%           
=======================================
  Files         139      139           
  Lines        5681     5685    +4     
=======================================
+ Hits         4978     4982    +4     
  Misses        420      420           
  Partials      283      283           

☔ 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 merged commit f54f368 into oras-project:main Sep 6, 2026
9 checks passed
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.

oras cp attributes destination "not found" errors to the source

2 participants