Retry image pulls on a mid-connection reset, not just dial failures

What does this MR do?

Adds "connection reset" to ClassifyImagePullFailure's retryable patterns, so a TCP connection reset mid-read/write against a registry or its auth endpoint is classified as RunnerExternalDependencyFailure and gets retried by shouldRetryImagePull, the same as a failed dial or a timeout already are.

Why was this MR needed?

shouldRetryImagePull (executors/docker/internal/pull/manager.go) only retries a failed pull when ClassifyImagePullFailure returns RunnerExternalDependencyFailure. That function recognized "dial tcp", "i/o timeout", "tls handshake timeout", and a couple of HTTP client-timeout shapes - but not a connection reset that happens after the connection is already established, e.g.:

failed to authorize: failed to fetch anonymous token: Get "https://auth.docker.io/token?...": read tcp 172.17.0.2:33222->172.64.144.78:443: read: connection reset by peer

That message falls through to the default ImagePullFailure case, which is not retried, so the runner gives up immediately on a condition that's exactly as transient as the cases that already retry.

This is a real production reliability gap, not just CI noise - any job pulling an image that hits a registry-side connection reset mid-request currently fails immediately instead of getting the retry it should. It surfaced repeatedly in this repo's own CI as intermittent integration test failures pulling from Docker Hub / mcr.microsoft.com:

All five hit this same code path; none of them are separately-caused test bugs, so one fix here covers both issues below. TestPwshGitCredHelper (also flagged as related in #39625 (closed)) pulls from mcr.microsoft.com, a different registry, and reproduces via the daemon's own image pull path the same way, so it should benefit too, though it wasn't separately verified against this exact code path.

What's the best way to test this MR?

TDD: added two failing test cases to common/classify_image_pull_failure_test.go (connection reset while reading, and while writing) reproducing the exact observed message shapes, confirmed they failed against the old classification (ImagePullFailure instead of RunnerExternalDependencyFailure), then added the "connection reset" match and confirmed both pass. go test ./common/... (TestClassifyImagePullFailure) and go test ./executors/docker/internal/pull/... both pass in full. go vet and gofmt are clean.

What are the relevant issue numbers?

Closes #39625 (closed) Closes #39627 (closed)

Merge request reports

Loading
Loading