juanfont/headscale · error
reading pull output: %w
Error message
reading pull output: %w
What it means
Returned inside `ensureImageAvailable`'s retry closure when the pull request succeeded but reading the pull output stream (`io.Copy` from the response reader to os.Stdout or io.Discard) failed. Docker's pull API delivers progress over this stream; a broken stream can also abort the pull server-side. It is retried under the same 60s backoff budget.
Source
Thrown at cmd/hi/docker.go:571
func() (struct{}, error) {
reader, pullErr := cli.ImagePull(ctx, imageName, image.PullOptions{RegistryAuth: registryAuth})
if pullErr != nil {
if isPermanentDockerPullError(pullErr) {
return struct{}{}, backoff.Permanent(pullErr)
}
return struct{}{}, fmt.Errorf("pulling image %s: %w", imageName, pullErr)
}
defer reader.Close()
sink := io.Discard
if verbose {
sink = os.Stdout
}
_, copyErr := io.Copy(sink, reader)
if copyErr != nil {
return struct{}{}, fmt.Errorf("reading pull output: %w", copyErr)
}
return struct{}{}, nil
},
backoff.WithBackOff(backoff.NewExponentialBackOff()),
backoff.WithMaxElapsedTime(60*time.Second),
)
if err != nil {
return err
}
if !verbose {
log.Printf("Image %s pulled successfully", imageName)
}
return nil
}
View on GitHub (pinned to 565fd254d0)
Solutions
- Pre-pull manually (`docker pull golang:<version>`) outside the test run so `hi` finds it locally and skips pulling entirely.
- Fix proxy/VPN stability for chunked streams.
- Re-run — transient resets are what the backoff exists for, though the budget is 60s.
- Check daemon logs if the stream breaks repeatedly at the same layer.
Example fix
# before: relying on hi to pull over a flaky proxy go run ./cmd/hi run TestX # reading pull output: connection reset # after: pre-pull separately, then run (image found locally) docker pull golang:1.26.1 && go run ./cmd/hi run TestX
Defensive patterns
Strategy: retry
Validate before calling
// eliminate the streaming path entirely by pre-pulling
if err := exec.Command("docker", "pull", "golang:"+cfg.GoVersion).Run(); err != nil {
return fmt.Errorf("pre-pull failed: %w", err)
} Try / catch
if err := ensureImageAvailable(ctx, cli, image, verbose); err != nil {
if strings.Contains(err.Error(), "reading pull output") {
// stream broke mid-pull: pre-pull manually to resume/complete layers, then re-run
}
} Prevention
- Pre-pull large base images in a separate CI step.
- Avoid proxies that truncate long-lived chunked responses for registry traffic.
- Keep the network stable through the pull window.
When it happens
Trigger: Connection reset mid-pull; proxy or VPN dropping long-lived chunked responses; daemon restarting while streaming layers; context cancellation arriving during the copy.
Common situations: Flaky networks pulling large golang images; corporate proxies with short idle timeouts; CI egress filtering that truncates streaming responses.
Related errors
- ensuring image availability: %w
- pulling image %s: %w
- executing test: %w
- getting container logs: %w
- DERPer is not ready: %w
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/06c90670ab39bacb.
Report an issue: GitHub.