From 6f83de36d59a129e3d03e6585679149610143fd0 Mon Sep 17 00:00:00 2001 From: Zane Bitter Date: Thu, 25 Jun 2026 21:00:46 +1200 Subject: [PATCH] fix(mirror): retry on ErrUnexpectedEOF and TLS errors When downloading blobs from CDN, dropped connections result in io.ErrUnexpectedEOF being propagated up to the retry wrapper. Similarly, CDN connectivity issues can produce TLS alert errors. Neither was classified as retryable, making --retry-times a no-op for these common failure modes. Assisted-by: Claude Code --- internal/pkg/mirror/mirror.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/internal/pkg/mirror/mirror.go b/internal/pkg/mirror/mirror.go index 9f1d04471..8d2caddb1 100644 --- a/internal/pkg/mirror/mirror.go +++ b/internal/pkg/mirror/mirror.go @@ -2,6 +2,7 @@ package mirror import ( "context" + "crypto/tls" "errors" "fmt" "io" @@ -229,6 +230,7 @@ func (o *Mirror) copy(ctx context.Context, src, dest string, opts *CopyOptions) // Custom implementation to extend `containers/common/pkg/retry.retry` func isErrorRetryable(err error) bool { var httpError docker.UnexpectedHTTPStatusError + var tlsErr tls.AlertError switch { case err == nil: return false @@ -236,6 +238,15 @@ func isErrorRetryable(err error) bool { return true case errors.Is(err, context.Canceled): return false + case errors.Is(err, io.ErrUnexpectedEOF): + return true + case errors.As(err, &tlsErr): + // Go's crypto/tls does not export alert code constants, so we cannot + // distinguish transient server-side errors from systematic ones. In + // practice, any systematic TLS failures (bad cert, version mismatch, + // etc.) are likely to occur before we get to the point of copying + // blobs, so we won't get here. + return true case errors.As(err, &httpError): // Retry on 500-504 server errors, they appear to be quite common in the field // We duplicate this here because older versions of oc-mirror cannot bump containers/common given Golang version restrictions