gohugoio/hugo · error

failed to read remote resource

Error message

failed to read remote resource %q: %w

What it means

Wrapped error from `Client.FromRemote` (remote.go:295-299) when `io.ReadAll(res.Body)` fails after a 2xx response. This is a mid-stream read failure on the response body -- the server returned a success status but the connection dropped or errored while streaming bytes. Distinct from 595 (status-level) and 598 (retry timeout).

Solutions

  1. Raise the per-request `timeout` option so the read completes: `(dict "timeout" "60s")`.
  2. For large assets, fetch and cache locally rather than streaming through Hugo.
  3. Retry the build (transient mid-stream failures often clear); the retry loop only covers request establishment, not body read, so a re-run is needed.
  4. If persistent, fetch the URL with `curl` from the build host to isolate server-side truncation.
  5. Reduce body size (server-side pagination) or fetch a compressed variant.

Example fix

// template -- before
{{ $r := resources.GetRemote $url }}

// after
{{ $r := resources.GetRemote $url (dict "timeout" "120s") }}
Defensive patterns

Strategy: retry

Validate before calling

// Head/probe to confirm a complete body is reachable.
// (Doesn't fully prevent mid-stream drops but surfaces instability early.)

Try / catch

// Retry on read-body failures.
for i := 0; i < 3; i++ {
	r, err := resources.GetRemote(url, opts)
	if err == nil || !strings.Contains(err.Error(), "failed to read remote resource") {
		return r, err
	}
	time.Sleep(time.Duration(i+1) * 2 * time.Second)
}

Prevention

When it happens

Trigger: Server returns 200 but closes the connection mid-body; TLS renegotiation failure during transfer; proxy buffering timeout; client-side deadline (`context.WithTimeout`) firing mid-read; very large body exhausting memory and failing allocation.

Common situations: Flaky upstream behind a CDN that truncates large payloads; slow origin server hitting the configured `timeout` after headers; CI runner with constrained memory fetching a large asset; corporate proxy killing long streams; intermittent network blip mid-download.

Related errors


AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09). Data as JSON: /api/errors/45f1d3a798467783. Report an issue: GitHub.

Appendix: source

Thrown at resources/resource_factories/create/remote.go:298

			// To cache this, we need to make sure the body is read.
			io.Copy(io.Discard, res.Body)
			return nil, nil
		}

		if res.StatusCode < 200 || res.StatusCode > 299 {
			return nil, toHTTPError(fmt.Errorf("failed to fetch remote resource from '%s': %s", uri, http.StatusText(res.StatusCode)), res, !isHeadMethod, options.ResponseHeaders)
		}

		var (
			body      []byte
			mediaType media.Type
		)
		// A response to a HEAD method should not have a body. If it has one anyway, that body must be ignored.
		// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD
		if !isHeadMethod && res.Body != nil {
			body, err = io.ReadAll(res.Body)
			if err != nil {
				return nil, fmt.Errorf("failed to read remote resource %q: %w", uri, err)
			}
		}

		filename := path.Base(rURL.Path)
		if _, params, _ := mime.ParseMediaType(res.Header.Get("Content-Disposition")); params != nil {
			if _, ok := params["filename"]; ok {
				filename = params["filename"]
			}
		}

		contentType := res.Header.Get("Content-Type")

		// For HEAD requests we have no body to work with, so we need to use the Content-Type header.
		if isHeadMethod || c.rs.ExecHelper.Sec().HTTP.MediaTypes.Accept(contentType) {
			var found bool
			mediaType, found = c.rs.MediaTypes().GetByType(contentType)
			if !found {
				// A media type not configured in Hugo, just create one from the content type string.

View on GitHub (pinned to 52c9bd7908)