charmbracelet/glow · error

unable to read http response body: %w

Error message

unable to read http response body: %w

What it means

After the API responds, glow reads the whole body with io.ReadAll(res.Body); this error wraps a failure or interruption during that read. The connection was established and a response started, but the body transfer broke partway. Subsequent code (JSON parse, status check) never runs.

Source

Thrown at github.go:35

		return nil, fmt.Errorf("invalid url: %s", u.String())
	}

	type readme struct {
		DownloadURL string `json:"download_url"`
	}

	apiURL := fmt.Sprintf("https://api.%s/repos/%s/%s/readme", u.Hostname(), owner, repo)

	//nolint:bodyclose
	// it is closed on the caller
	res, err := http.Get(apiURL) //nolint: gosec,noctx
	if err != nil {
		return nil, fmt.Errorf("unable to get url: %w", err)
	}

	body, err := io.ReadAll(res.Body)
	if err != nil {
		return nil, fmt.Errorf("unable to read http response body: %w", err)
	}

	var result readme
	if err := json.Unmarshal(body, &result); err != nil {
		return nil, fmt.Errorf("unable to parse json: %w", err)
	}

	if res.StatusCode == http.StatusOK {
		//nolint:bodyclose
		// it is closed on the caller
		resp, err := http.Get(result.DownloadURL) //nolint: noctx
		if err != nil {
			return nil, fmt.Errorf("unable to get url: %w", err)
		}

		if resp.StatusCode == http.StatusOK {
			return &source{resp.Body, result.DownloadURL}, nil
		}

View on GitHub (pinned to e3970c813d)

Solutions

  1. Retry — transient mid-body resets usually clear on a second attempt
  2. If persistent, capture where it dies: curl --raw -o /dev/null -w '%{size_download}' https://api.github.com/...
  3. Bypass the suspect intermediary (different network, disable VPN, direct DNS) to identify the cutter
  4. Fall back to the raw file URL or a local copy so the API body is not needed

Example fix

# transient: just retry
glow https://github.com/owner/repo || glow https://github.com/owner/repo

# if persistent, bypass the API body
glow https://raw.githubusercontent.com/owner/repo/main/README.md
Defensive patterns

Strategy: retry

Try / catch

body, err := io.ReadAll(res.Body)
if err != nil {
	var ne net.Error
	if errors.As(err, &ne) && ne.Timeout() || errors.Is(err, syscall.ECONNRESET) {
		// mid-body transport failure: safe to retry the whole request idempotently
		for i := 0; i < 3; i++ {
			res2, err2 := http.Get(apiURL)
			if err2 == nil {
				body, err = io.ReadAll(res2.Body)
				_ = res2.Body.Close()
				if err == nil {
					break
				}
			}
			time.Sleep(time.Duration(i+1) * 250 * time.Millisecond)
		}
	}
	if err != nil {
		return nil, fmt.Errorf("unable to read http response body: %w", err)
	}
}

Prevention

When it happens

Trigger: Connection reset by peer or proxy mid-body; response truncated by a flaky NAT/VPN; keep-alive race where the server closes the connection during the read; captive portals that accept the handshake then cut the stream; timeouts enforced by an intermediary.

Common situations: Unstable Wi-Fi/VPN links; aggressive corporate proxies with short body-read timeouts; cloud NAT dropping long-lived sockets; intermittent ISP interception of GitHub traffic.

Related errors


AI-assisted analysis of charmbracelet/glow@e3970c813d (2026-08-15). Data as JSON: /api/errors/8399a789e469bc22. Report an issue: GitHub.