air-verse/air · error

proxy inject: failed to init gzip reader: %w

Error message

proxy inject: failed to init gzip reader: %w

What it means

When proxying, injectLiveReload detects a gzip Content-Encoding header and wraps resp.Body in gzip.NewReader to read the decompressed HTML before injecting the reload script. If the gzip stream is corrupt or empty, this error is returned.

Source

Thrown at runner/proxy.go:125

}

func (p *Proxy) Reload() {
	p.stream.Reload()
}

func (p *Proxy) BuildFailed(msg BuildFailedMsg) {
	p.stream.BuildFailed(msg)
}

func (p *Proxy) injectLiveReload(resp *http.Response) (string, bool, error) {
	var reader io.Reader = resp.Body
	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 {

View on GitHub (pinned to 71ea1dee05)

Solutions

  1. Fix the upstream app so it only sets Content-Encoding: gzip when it actually compresses the body
  2. Disable compression in the upstream app/middleware so air receives plain responses
  3. Check the wrapped %w error: gzip.ErrHeader / unexpected EOF indicate corrupt or truncated streams
  4. Bypass other compression layers (CDN/ingress) between air's proxy and the app

Example fix

// before (upstream app)
w.Header().Set("Content-Encoding", "gzip")
w.Write([]byte(html)) // not actually gzipped
// after
w.Write([]byte(html)) // let the framework compress or drop the header
Defensive patterns

Strategy: try-catch

Validate before calling

// verify upstream doesn't lie about gzip
resp, _ := http.Get("http://localhost:" + appPort + "/")
if resp.Header.Get("Content-Encoding") == "gzip" {
	zr, err := gzip.NewReader(resp.Body)
	if err != nil {
		log.Printf("upstream sends invalid gzip: %v", err)
	}
	_ = zr
}

Try / catch

if err := startProxy(); err != nil && strings.Contains(err.Error(), "failed to init gzip reader") {
	// disable compression upstream, then retry
}

Prevention

When it happens

Trigger: The upstream app responds with Content-Encoding: gzip but the body is not valid gzip data — e.g. the app sets the header manually but sends uncompressed bytes, or the stream is truncated.

Common situations: Backend frameworks double-encoding or lying about the encoding header; middleware setting Content-Encoding: gzip without actually compressing; proxying non-Golang servers with broken compression middleware.

Related errors


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