{"record":{"id":"bab31f9cd85db678","repo":"charmbracelet/glow","slug":"unable-to-read-http-response-body-w-bab31f","errorCode":null,"errorMessage":"unable to read http response body: %w","messagePattern":"unable to read http response body: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gitlab.go","lineNumber":37,"sourceCode":"\n\tprojectPath := url.QueryEscape(owner + \"/\" + repo)\n\n\ttype readme struct {\n\t\tReadmeURL string `json:\"readme_url\"`\n\t}\n\n\tapiURL := fmt.Sprintf(\"https://%s/api/v4/projects/%s\", u.Hostname(), projectPath)\n\n\t//nolint:bodyclose\n\t// it is closed on the caller\n\tres, err := http.Get(apiURL) //nolint: gosec,noctx\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to get url: %w\", err)\n\t}\n\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to read http response body: %w\", err)\n\t}\n\n\tvar result readme\n\tif err := json.Unmarshal(body, &result); err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to parse json: %w\", err)\n\t}\n\n\treadmeRawURL := strings.ReplaceAll(result.ReadmeURL, \"blob\", \"raw\")\n\n\tif res.StatusCode == http.StatusOK {\n\t\t//nolint:bodyclose\n\t\t// it is closed on the caller\n\t\tresp, err := http.Get(readmeRawURL) //nolint: gosec,noctx\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to get url: %w\", err)\n\t\t}\n\n\t\tif resp.StatusCode == http.StatusOK {","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/charmbracelet/glow/blob/e3970c813d93da1ea84edfca90c289d9afb82242/gitlab.go#L19-L55","documentation":"glow fetches GitLab READMEs via the v4 REST API: it builds https://<host>/api/v4/projects/<path>, calls http.Get, then streams the body with io.ReadAll. This error means the HTTP request itself succeeded but reading the response body failed; the underlying I/O error is wrapped with %w. It almost always indicates a connection that dropped or was cut after the response headers were received.","triggerScenarios":"http.Get to the GitLab projects API returns headers, then the TCP connection resets or times out mid-body; a proxy or middlebox truncates the transfer; a TLS problem surfaces during body streaming; the server closes the connection early.","commonSituations":"Flaky Wi-Fi or VPN links, corporate proxies that cut long streams, self-hosted GitLab behind nginx/traefik with an aggressive proxy_read_timeout, CI runners with restricted egress.","solutions":["Retry the command - transient connection drops are the most common cause","Reproduce outside glow: curl https://gitlab.com/api/v4/projects/<owner>%2F<repo> and watch for truncation","For self-hosted GitLab, raise reverse-proxy read timeouts (e.g. nginx proxy_read_timeout)","Test from a different network to rule out VPN/proxy interference"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"func gitlabAPIReadable(host, projectPath string) error {\n\tu := fmt.Sprintf(\"https://%s/api/v4/projects/%s\", host, projectPath)\n\tclient := &http.Client{Timeout: 15 * time.Second}\n\tres, err := client.Get(u) //nolint:noctx\n\tif err != nil { return err }\n\tdefer res.Body.Close()\n\t_, err = io.Copy(io.Discard, io.LimitReader(res.Body, 1<<20))\n\treturn err\n}","typeGuard":"func isTransientNetErr(err error) bool {\n\tvar dnsErr *net.DNSError\n\tif errors.As(err, &dnsErr) { return dnsErr.IsTemporary }\n\tvar opErr *net.OpError\n\treturn errors.As(err, &opErr)\n}","tryCatchPattern":"var body []byte\nfor attempt := 0; attempt < 3; attempt++ {\n\tvar err error\n\tbody, err = fetchGitLabAPI(u)\n\tif err == nil { break }\n\tif !isTransientNetErr(err) { return err }\n\ttime.Sleep(time.Duration(attempt+1) * 500 * time.Millisecond)\n}","preventionTips":["Use an http.Client with an explicit Timeout instead of bare http.Get so stalled bodies fail fast","Retry transient network errors with backoff before surfacing them to the user","Watch for proxy interference when fetching from self-hosted GitLab"],"tags":["network","gitlab","http","io"],"backgroundTag":null,"analyzedSha":"e3970c813d93da1ea84edfca90c289d9afb82242","analyzedAt":"2026-08-15T22:50:54.304Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}