air-verse/air · error

proxy inject: failed to read body from http response: %w

Error message

proxy inject: failed to read body from http response: %w

What it means

After optionally decompressing, injectLiveReload reads the entire response body into a buffer so the live-reload script can be injected before </body>. Any I/O failure reading the body is wrapped in this error.

Source

Thrown at runner/proxy.go:137

	decoded := false

	switch detectContentEncoding(resp.Header) {
	case encodingGzip:
		gzipReader, err := gzip.NewReader(resp.Body)
		if err != nil {
			return "", false, fmt.Errorf("proxy inject: failed to init gzip reader: %w", err)
		}
		defer gzipReader.Close()
		reader = gzipReader
		decoded = true
	case encodingBrotli:
		reader = brotli.NewReader(resp.Body)
		decoded = true
	}

	buf := new(bytes.Buffer)
	if _, err := buf.ReadFrom(reader); err != nil {
		return "", decoded, fmt.Errorf("proxy inject: failed to read body from http response: %w", err)
	}
	page := buf.String()

	// the script will be injected before the end of the body tag. In case the tag is missing, the injection will be skipped with no error.
	body := strings.LastIndex(page, "</body>")
	if body == -1 {
		return page, decoded, nil
	}

	script := "<script>" + ProxyScript + "</script>"
	return page[:body] + script + page[body:], decoded, nil
}

func (p *Proxy) proxyHandler(w http.ResponseWriter, r *http.Request) {
	appURL := r.URL
	appURL.Scheme = "http"
	appURL.Host = fmt.Sprintf("localhost:%d", p.config.AppPort)

View on GitHub (pinned to 71ea1dee05)

Solutions

  1. Check the wrapped %w error for the underlying I/O cause and whether the upstream app crashed mid-response
  2. Restart/rebuild the dev application so it serves complete responses
  3. Disable upstream compression (brotli/gzip) if brotli streams are the trigger
  4. Check network paths between air's proxy and the app (localhost should be reliable; remote targets less so)

Example fix

// before: proxying to a remote, flaky target
app_port = 3000 // remote host resets connections
// after: proxy to a stable local dev server
app_port = 8080 // local server that keeps connections open
Defensive patterns

Strategy: retry

Validate before calling

// check the app is up and stable before proxying
resp, err := http.Get("http://localhost:" + appPort + "/")
if err != nil {
	return fmt.Errorf("app not reachable: %w", err)
}
io.Copy(io.Discard, resp.Body) // ensure body reads fully

Try / catch

if err := startProxy(); err != nil && strings.Contains(err.Error(), "failed to read body") {
	// app likely reset mid-response; wait and retry
	time.Sleep(500 * time.Millisecond)
	_ = startProxy()
}

Prevention

When it happens

Trigger: injectLiveReload (called from proxyHandler) gets an I/O error while buf.ReadFrom reads the upstream body — connection reset by the app mid-response, aborted chunked encoding, or a failing brotli reader on a corrupt stream.

Common situations: The dev server crashes or closes connections mid-response while air is proxying; flaky upstream (hot-reload restarts); corrupt brotli-encoded responses.

Related errors


AI-assisted analysis of air-verse/air@71ea1dee05 (2026-08-31). Data as JSON: /api/errors/3fcdcd35a983c9fa. Report an issue: GitHub.