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

  1. Pre-pull manually (`docker pull golang:<version>`) outside the test run so `hi` finds it locally and skips pulling entirely.
  2. Fix proxy/VPN stability for chunked streams.
  3. Re-run — transient resets are what the backoff exists for, though the budget is 60s.
  4. 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

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


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/06c90670ab39bacb. Report an issue: GitHub.